pax_global_header00006660000000000000000000000064152606420220014511gustar00rootroot0000000000000052 comment=dcb56dd4b4dc9f989d12fc34e32819f4937244b0 odyssey-1.6.0-rc1/000077500000000000000000000000001526064202200136775ustar00rootroot00000000000000odyssey-1.6.0-rc1/.clang-format000066400000000000000000000077271526064202200162670ustar00rootroot00000000000000# from https://github.com/torvalds/linux/blob/master/.clang-format # SPDX-License-Identifier: GPL-2.0 # # clang-format configuration file. Intended for clang-format >= 4. # # For more information, see: # # Documentation/process/clang-format.rst # https://clang.llvm.org/docs/ClangFormat.html # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left # Unknown to clang-format-4.0 AlignOperands: true InsertNewlineAtEOF: true AlignConsecutiveMacros: false AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true InsertBraces: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: false AfterUnion: false #AfterExternBlock: false # Unknown to clang-format-5.0 BeforeCatch: false BeforeElse: false IndentBraces: false #SplitEmptyFunction: true # Unknown to clang-format-4.0 #SplitEmptyRecord: true # Unknown to clang-format-4.0 #SplitEmptyNamespace: true # Unknown to clang-format-4.0 BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom #BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false #BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' #CompactNamespaces: false # Unknown to clang-format-4.0 ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false #FixNamespaceComments: false # Unknown to clang-format-4.0 ForEachMacros: - 'od_list_foreach' - 'od_list_foreach_safe' - 'mm_list_foreach' - 'mm_list_foreach_safe' #IncludeBlocks: Preserve # Unknown to clang-format-5.0 IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false #IndentPPDirectives: None # Unknown to clang-format-5.0 IndentWidth: 8 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None #ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 ObjCBlockIndentWidth: 8 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true # Taken from git's rules #PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: false #SortUsingDeclarations: false # Unknown to clang-format-4.0 SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true #SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 #SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 SpaceBeforeParens: ControlStatements #SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 8 UseTab: Always ... odyssey-1.6.0-rc1/.dockerignore000066400000000000000000000000521526064202200163500ustar00rootroot00000000000000build prometheus/exporter/odyssey_exporterodyssey-1.6.0-rc1/.git-blame-ignore-revs000066400000000000000000000000521526064202200177740ustar00rootroot00000000000000f0ce56ec3b3d8b742bd15a59ca35c781488c27dc odyssey-1.6.0-rc1/.github/000077500000000000000000000000001526064202200152375ustar00rootroot00000000000000odyssey-1.6.0-rc1/.github/dependabot.yml000066400000000000000000000012021526064202200200620ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: /docker/config-validation/ schedule: interval: daily - package-ecosystem: gomod directory: /docker/ody_integration_test/ schedule: interval: daily - package-ecosystem: gomod directory: /docker/prep_stmts/ schedule: interval: daily - package-ecosystem: gomod directory: /benchmarks/client_max_routing/ schedule: interval: daily - package-ecosystem: gomod directory: /prometheus/exporter/ schedule: interval: daily - package-ecosystem: github-actions directory: / schedule: interval: daily odyssey-1.6.0-rc1/.github/workflows/000077500000000000000000000000001526064202200172745ustar00rootroot00000000000000odyssey-1.6.0-rc1/.github/workflows/async-notification.yml000066400000000000000000000011541526064202200236210ustar00rootroot00000000000000name: Async notification proxy on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: async-notification: name: async-notification runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Test with async notifications against Odyssey run: make async-notice-test odyssey-1.6.0-rc1/.github/workflows/build.yml000066400000000000000000000044721526064202200211250ustar00rootroot00000000000000name: build on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "CMakeLists.txt" - "test/**" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: ubuntu-build: name: ubuntu-build runs-on: ubuntu-latest strategy: matrix: os-version: ['jammy', 'noble', 'plucky', 'questing', 'resolute'] build-type: ['build_release'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Ubuntu build check run: make ci-build-check-ubuntu ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_TEST_CODENAME=${{ matrix.os-version }} fedora-build: name: fedora-build runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Fedora build check run: make ci-build-check-fedora oraclelinux-build: name: oraclelinux-build runs-on: ubuntu-latest strategy: matrix: os-version: ['8', '9'] build-type: ['build_release'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Oraclelinux build check run: make ci-build-check-oracle-linux ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_ORACLELINUX_VERSION=${{ matrix.os-version }} debian-build: name: debian-build runs-on: ubuntu-latest strategy: matrix: os-version: ['bookworm', 'trixie', 'forky', 'sid'] build-type: ['build_release'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Debian build check run: make ci-build-check-debian ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_TEST_DEBIAN_DISTRO=${{ matrix.os-version }} macos-build: name: macos-build runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - runner: macos-14 # Apple Silicon (arm64) steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Install dependencies run: | brew update brew install openssl@3 openldap bison flex - name: Build and run unit tests run: make ci-build-check-macos odyssey-1.6.0-rc1/.github/workflows/coverity.yml000066400000000000000000000025641526064202200216720ustar00rootroot00000000000000name: coverity on: schedule: - cron: "0 0 * * *" jobs: scan: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'yandex' }} env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} steps: - uses: actions/checkout@v7 - name: Cache Coverity build tool uses: actions/cache@v6 with: path: | coverity_tool.tar.gz key: ${{ runner.os }} - name: Download Coverity build tool run: | wget -c -N https://scan.coverity.com/download/linux64 --post-data "token=$TOKEN&project=yandex%2Fodyssey" -O coverity_tool.tar.gz mkdir coverity_tool tar xzf coverity_tool.tar.gz --strip 1 -C coverity_tool - name: Install dependencies run: sudo bash scripts/install_ci.sh - name: Pre-build run: cmake -DCMAKE_BUILD_TYPE=Release - name: Build with Coverity build tool run: | export PATH=`pwd`/coverity_tool/bin:$PATH cov-build --dir cov-int make -j2 - name: Submit build result to Coverity Scan run: | tar czvf odyssey.tar.gz cov-int curl --form token=$TOKEN \ --form email=amborodin@acm.org \ --form file=@odyssey.tar.gz \ --form version="Commit $GITHUB_SHA" \ --form description="Build submitted via CI" \ https://scan.coverity.com/builds?project=yandex%2Fodyssey odyssey-1.6.0-rc1/.github/workflows/docker-dev.yml000066400000000000000000000040441526064202200220440ustar00rootroot00000000000000name: Docker Dev Build on: push: branches: - master - 'REL_*' paths-ignore: - "sources/machinarium/gdb/**" pull_request: branches: - master paths-ignore: - "sources/machinarium/gdb/**" env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v7 with: submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Log in to Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v4.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | # main/master → edge type=raw,value=edge,enable={{is_default_branch}} # ветка → branch-name type=ref,event=branch # pr-123 type=ref,event=pr # git sha type=sha,prefix={{branch}}-,enable=${{ github.event_name != 'pull_request' }} labels: | org.opencontainers.image.title=Odyssey PostgreSQL Pooler org.opencontainers.image.description=Scalable PostgreSQL connection pooler org.opencontainers.image.vendor=Yandex org.opencontainers.image.licenses=BSD-3-Clause - name: Build and push Docker image uses: docker/build-push-action@v7 with: context: . file: ./docker/quickstart/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max odyssey-1.6.0-rc1/.github/workflows/docker-release.yml000066400000000000000000000035261526064202200227120ustar00rootroot00000000000000name: Docker Release on: push: tags: - 'v*.*.*' - 'v*.*.*-rc*' - 'v*.*.*-beta*' - 'v*.*.*-alpha*' env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: write packages: write steps: - name: Checkout repository uses: actions/checkout@v7 with: submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Log in to Container Registry uses: docker/login-action@v4.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | # latest type=raw,value=latest,enable=${{ !contains(github.ref, '-') }} # v1.2.3 → 1.2.3 type=semver,pattern={{version}} # v1.2.3 → 1.2 type=semver,pattern={{major}}.{{minor}} # v1.2.3 → 1 type=semver,pattern={{major}} labels: | org.opencontainers.image.title=Odyssey PostgreSQL Pooler org.opencontainers.image.description=Scalable PostgreSQL connection pooler org.opencontainers.image.vendor=Yandex org.opencontainers.image.licenses=BSD-3-Clause - name: Build and push Docker image uses: docker/build-push-action@v7 with: context: . file: ./docker/quickstart/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max odyssey-1.6.0-rc1/.github/workflows/drivers-tests.yml000066400000000000000000000016221526064202200226360ustar00rootroot00000000000000name: Drivers tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: jdbc: name: jdbc runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: docker buildx uses: docker/setup-buildx-action@v4 - name: Odyssey JDBC compatibility tests run: make jdbc_test userver: name: userver runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: docker buildx uses: docker/setup-buildx-action@v4 - name: Odyssey userver compatibility tests run: make userver_test odyssey-1.6.0-rc1/.github/workflows/format.yml000066400000000000000000000006311526064202200213070ustar00rootroot00000000000000name: Format on: push: branches: [ master, 'REL_*' ] paths-ignore: - "sources/machinarium/gdb/**" pull_request: branches: [ master ] paths-ignore: - "sources/machinarium/gdb/**" jobs: format: name: format runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Check format run: make check-format odyssey-1.6.0-rc1/.github/workflows/functional-tests.yml000066400000000000000000000014561526064202200233270ustar00rootroot00000000000000name: Functional tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: functional: name: Functional runs-on: ubuntu-latest timeout-minutes: 60 strategy: fail-fast: false matrix: build-type: ['build_release', 'build_asan', 'build_dbg'] cc: ['clang', 'gcc'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey functional tests run: make functional-test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_CC=${{ matrix.cc }} odyssey-1.6.0-rc1/.github/workflows/pg_regress.yml000066400000000000000000000014261526064202200221620ustar00rootroot00000000000000name: pg_regress tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: regress_tests: name: pg_regress runs-on: ubuntu-latest strategy: fail-fast: false matrix: build-type: ['build_release', 'build_asan', 'build_dbg'] cc: ['clang', 'gcc'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey pg_regress tests run: make regress_test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_CC=${{ matrix.cc }} odyssey-1.6.0-rc1/.github/workflows/prom-exporter-test.yml000066400000000000000000000007111526064202200236160ustar00rootroot00000000000000name: Check prometheus exporter work on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "docker/prom-exporter/**" - "prometheus/**" - "test/prom-exporter/**" jobs: prom-exporter: name: prom-exporter runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Check prom exporter works run: make prom-exporter-test odyssey-1.6.0-rc1/.github/workflows/prometheus-test.yml000066400000000000000000000013201526064202200231630ustar00rootroot00000000000000name: Build with prometheus on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: prom: name: prom runs-on: ubuntu-latest strategy: matrix: build-type: ['build_release', 'build_dbg', 'build_asan'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey build with prometheus test run: make prometheus-legacy-test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} odyssey-1.6.0-rc1/.github/workflows/proto-tests.yml000066400000000000000000000015541526064202200223270ustar00rootroot00000000000000name: Protocol tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: protocol: name: protocol runs-on: ubuntu-latest timeout-minutes: 60 strategy: fail-fast: false matrix: build-type: ['build_release', 'build_asan', 'build_dbg'] cc: ['clang', 'gcc'] tag: ['simple', 'copy', 'xproto'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey protocol tests run: make proto-test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_CC=${{ matrix.cc }} PROTO_TEST_TAG=${{ matrix.tag }} odyssey-1.6.0-rc1/.github/workflows/quickstart-alpine-test.yml000066400000000000000000000011371526064202200244360ustar00rootroot00000000000000name: Quickstart image test on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: quickstart-image-build: name: quickstart-image-build runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey quickstart image test run: make quickstart_test odyssey-1.6.0-rc1/.github/workflows/replication-test.yml000066400000000000000000000011361526064202200233060ustar00rootroot00000000000000name: Replication test on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: replication-test: name: replication-test runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Replication protocol with Odyssey image test run: make replication_test odyssey-1.6.0-rc1/.github/workflows/scram-passthrough.yml000066400000000000000000000014771526064202200235020ustar00rootroot00000000000000name: SCRAM-passthrough tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: scram_passthrough_tests: name: scram_passthrough runs-on: ubuntu-latest strategy: fail-fast: false matrix: build-type: ['build_release', 'build_asan', 'build_dbg'] cc: ['clang', 'gcc'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey scram_passthrough tests run: make scram_passthrough_test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_CC=${{ matrix.cc }} odyssey-1.6.0-rc1/.github/workflows/soft-oom.yml000066400000000000000000000010531526064202200215610ustar00rootroot00000000000000name: Soft oom on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "docker/**" - "CMakeLists.txt" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: soft_oom: name: soft_oom runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey soft oom test run: make soft-oom-testodyssey-1.6.0-rc1/.github/workflows/stress-tests.yml000066400000000000000000000013161526064202200225030ustar00rootroot00000000000000name: Stress tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: functional: name: Stress runs-on: ubuntu-latest strategy: fail-fast: false matrix: build-type: ['build_release', 'build_asan', 'build_dbg'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: Odyssey stress tests run: make stress-tests ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} odyssey-1.6.0-rc1/.github/workflows/unit-tests.yml000066400000000000000000000017331526064202200221420ustar00rootroot00000000000000name: Unit tests on: push: branches: [ master, 'REL_*' ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" - "!test/prom-exporter/**" - "!sources/machinarium/gdb/**" jobs: unit: name: unit runs-on: ubuntu-latest timeout-minutes: 20 strategy: matrix: build-type: ['build_release', 'build_dbg', 'build_asan'] target-arch: ['linux/amd64'] cc: ['gcc', 'clang'] steps: - uses: actions/checkout@v7 with: submodules: recursive - name: qemu setup uses: docker/setup-qemu-action@v4 - name: docker buildx uses: docker/setup-buildx-action@v4 - name: Odyssey unit tests run: make ci-unittests ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_TEST_TARGET_PLATFORM=${{ matrix.target-arch }} ODYSSEY_CC=${{ matrix.cc }} odyssey-1.6.0-rc1/.gitignore000066400000000000000000000016661526064202200157000ustar00rootroot00000000000000*.o *.a *.so .idea/ .init *.swp .cache/ .conf.example .logrotate .vscode/ sources/odyssey sources/build.h stress/odyssey_stress test/odyssey_test test/odyssey/data sources/machinarium/sources/build.h sources/machinarium/benchmark/benchmark_csw sources/machinarium/benchmark/benchmark_csw2 sources/machinarium/benchmark/benchmark_channel sources/machinarium/benchmark/benchmark_channel_shared build/ build-asan/ cmake-build-debug/ test/shell-test/odyssey tmp/ test-reports/ test/ody_integration_test/main docker/bin/odyssey docker/bin/odyssey-asan docker/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console/bin docker/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console/obj odyssey.conf.example odyssey.init odyssey.logrotate .DS_Store cscope.out prometheus/exporter/odyssey_exporter site/ /*.patch docs/tf/.terraform.lock.hcl docs/tf/.terraform/ docs/tf/terraform.tfstate docs/tf/terraform.tfstate.backup docs/tf/terraform.tfvars odyssey-1.6.0-rc1/.gitmodules000066400000000000000000000000001526064202200160420ustar00rootroot00000000000000odyssey-1.6.0-rc1/AUTHORS000066400000000000000000000004351526064202200147510ustar00rootroot00000000000000The following authors have created the source code of "Odyssey" published and distributed by YANDEX LLC as the owner: Dmitry Simonenko Dmitry Sarafannikov Evgeniy Efimkin Andrey Borodin odyssey-1.6.0-rc1/CMakeLists.txt000066400000000000000000000225541526064202200164470ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(odyssey C) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_C_STANDARD 11) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(ODYSSEY_NAME "odyssey") if(EXISTS "${CMAKE_SOURCE_DIR}/VERSION") file(READ "${CMAKE_SOURCE_DIR}/VERSION" ODYSSEY_VERSION_NUMBER) string(STRIP "${ODYSSEY_VERSION_NUMBER}" ODYSSEY_VERSION_NUMBER) message(STATUS "Version from VERSION file: ${ODYSSEY_VERSION_NUMBER}") else() set(ODYSSEY_VERSION_NUMBER "unknown") message(WARNING "VERSION file not found, using 'unknown'") endif() find_package(BISON REQUIRED) find_package(FLEX REQUIRED) find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE ODYSSEY_VERSION_GIT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) endif() if(DEFINED ENV{ODYSSEY_NAME}) set(ODYSSEY_NAME $ENV{ODYSSEY_NAME}) endif() if(DEFINED ENV{ODYSSEY_VERSION}) set(ODYSSEY_VERSION_NUMBER $ENV{ODYSSEY_VERSION}) endif() if(DEFINED ENV{ODYSSEY_GIT_HASH}) set(ODYSSEY_VERSION_GIT $ENV{ODYSSEY_GIT_HASH}) endif() if("${ODYSSEY_VERSION_GIT}" STREQUAL "") set(ODYSSEY_VERSION_FULL "${ODYSSEY_VERSION_NUMBER}") else() set(ODYSSEY_VERSION_FULL "${ODYSSEY_VERSION_NUMBER}-${ODYSSEY_VERSION_GIT}") endif() message(STATUS "") message(STATUS "Odyssey version configuration:") message(STATUS " Name: ${ODYSSEY_NAME}") message(STATUS " Version number: ${ODYSSEY_VERSION_NUMBER}") message(STATUS " Git hash: ${ODYSSEY_VERSION_GIT}") message(STATUS " Full version: ${ODYSSEY_VERSION_FULL}") message(STATUS "") # Extract compiler name and version separately for clean metrics # Use CMAKE_C_COMPILER_ID for reliable detection (GNU, Clang, MSVC, etc.) if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") set(ODYSSEY_COMPILER_NAME "gcc") elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") set(ODYSSEY_COMPILER_NAME "clang") else() # Fallback to the compiler executable name get_filename_component(ODYSSEY_COMPILER_NAME ${CMAKE_C_COMPILER} NAME) endif() set(ODYSSEY_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}") if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(BUILD_BITS "64-bit") else() set(BUILD_BITS "32-bit") endif() # Keep full string for backward compatibility in logs set(ODYSSEY_COMPILER_STRING "${ODYSSEY_COMPILER_NAME}-${ODYSSEY_COMPILER_VERSION}, ${BUILD_BITS}") set(OD_DEVEL_LVL -1) set(COMMON_CFLAGS "-pedantic -Wall -Wextra -Werror -Wstrict-aliasing -pthread -g") # enable 128-bit CAS (cmpxchg16b) on x86-64 for lock-free 16-byte atomics. # On aarch64 it is available without any flag. # The lf-stack uses __sync builtins (not ) for 128-bit CAS, # which are always inlined by the compiler and never require libatomic. if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64") set(COMMON_CFLAGS "${COMMON_CFLAGS} -mcx16") endif() if ("${CMAKE_BUILD_TYPE}" STREQUAL "") set(CMAKE_BUILD_TYPE "Debug") set(OD_DEVEL_LVL 1) endif() if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -O2") set(OD_DEVEL_LVL -1) elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDbgInfo") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -O2 -fno-omit-frame-pointer") set(OD_DEVEL_LVL -1) elseif("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -O0") set(OD_DEVEL_LVL 1) elseif("${CMAKE_BUILD_TYPE}" STREQUAL "ASAN") if (APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer -O0") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -fsanitize=address,undefined,leak -fno-sanitize-recover=all -fno-omit-frame-pointer -O0") endif() set(HAVE_ASAN 1) elseif("${CMAKE_BUILD_TYPE}" STREQUAL "TSAN") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -fsanitize=thread -fno-sanitize-recover=all -fno-omit-frame-pointer -O0") set(HAVE_TSAN 1) else() message(FATAL_ERROR "Unknown build type ${CMAKE_BUILD_TYPE}") endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" ODYSSEY_BUILD_TYPE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CC_FLAGS}") set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) add_custom_target(build_libs) if (APPLE) # macOS: librt/libdl functionality is part of the base libSystem, # there are no separate librt.dylib / libdl.dylib to link against. set(od_libraries "") else() set(od_libraries "rt" "dl") endif() set(OPENSSL_INCLUDE_DIR $ENV{OPENSSL_INCLUDE_DIR}) set(OPENSSL_LIBRARIES $ENV{OPENSSL_LIBRARIES}) set(OPENSSL_VERSION $ENV{OPENSSL_VERSION}) # use BoringSSL or OpenSSL option(USE_BORINGSSL "Use BoringSSL" OFF) if (USE_BORINGSSL) find_package(BoringSSL REQUIRED) if (BORINGSSL_FOUND) set(od_ssl ${BORINGSSL_LIBRARIES}) include_directories(${BORINGSSL_INCLUDE_DIR}) endif () elseif ("${OPENSSL_LIBRARIES}" STREQUAL "") find_package(OpenSSL REQUIRED) set(od_ssl ${OPENSSL_LIBRARIES}) include_directories(${OPENSSL_INCLUDE_DIR}) add_definitions(-DUSE_SSL) else () set(od_ssl ${OPENSSL_LIBRARIES}) include_directories(${OPENSSL_INCLUDE_DIR}) add_definitions(-DUSE_SSL) endif () # use PAM find_package(PAM) if (PAM_FOUND) set(od_pam ${PAM_LIBRARY}) include_directories(${PAM_INCLUDE_DIR}) endif() set(od_libraries ${od_libraries} ${od_pam}) # use LDAP find_package(LDAP) if (LDAP_FOUND) set(od_ldap ${LDAP_LIBRARY}) include_directories(${LDAP_INCLUDE_DIR}) endif() set(od_libraries ${od_libraries} ${od_ldap}) # use systemd (for sd_notify support) find_path(SYSTEMD_INCLUDE_DIR NAMES systemd/sd-daemon.h HINTS /usr/include /usr/local/include ) find_library(SYSTEMD_LIBRARY NAMES systemd HINTS /usr/lib /usr/local/lib ) if (SYSTEMD_INCLUDE_DIR AND SYSTEMD_LIBRARY) set(od_systemd ${SYSTEMD_LIBRARY}) include_directories(${SYSTEMD_INCLUDE_DIR}) add_definitions(-DHAVE_SYSTEMD) set(SYSTEMD_FOUND TRUE) else() message(WARNING "SYSTEMD library not found!!!") set(SYSTEMD_FOUND FALSE) endif() set(od_libraries ${od_libraries} ${od_systemd}) # use Prom find_package(Prom) find_package(Promhttp) if (PROM_FOUND AND PROMHTTP_FOUND) set(od_prom ${PROM_LIBRARY}) include_directories(${PROM_INCLUDE_DIR}) set(od_promhttp ${PROMHTTP_LIBRARY}) include_directories(${PROMHTTP_INCLUDE_DIR}) endif() set(od_libraries ${od_libraries} ${od_prom}) set(od_libraries ${od_libraries} ${od_promhttp}) set(od_libraries ${od_libraries} ${od_ssl}) #compression option(BUILD_COMPRESSION "Build with compression support" OFF) if(BUILD_COMPRESSION) set(compression_libraries "") add_definitions(-DMM_BUILD_COMPRESSION) # use zstd find_package(Zstd) if(ZSTD_FOUND) include_directories(${ZSTD_INCLUDE_DIR}) set(compression_libraries ${compression_libraries} ${ZSTD_LIBRARY}) add_definitions(-DMM_HAVE_ZSTD) endif() # use zlib find_package(ZLIB) if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIRS}) set(compression_libraries ${compression_libraries} ${ZLIB_LIBRARIES}) add_definitions(-DMM_HAVE_ZLIB) endif() endif() message(STATUS "") message(STATUS "================================================================") message(STATUS "Odyssey ${ODYSSEY_VERSION_FULL}") message(STATUS "") message(STATUS "Build configuration:") message(STATUS " Build type: ${CMAKE_BUILD_TYPE}") message(STATUS " C Compiler: ${ODYSSEY_COMPILER_STRING}") message(STATUS " C Flags: ${CMAKE_C_FLAGS}") message(STATUS " Platform: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS " C lang standard: ${CMAKE_C_STANDARD}") message(STATUS "") message(STATUS "BUILD_DEBIAN: ${BUILD_DEBIAN}") message(STATUS "USE_BORINGSSL: ${USE_BORINGSSL}") message(STATUS "BORINGSSL_ROOT_DIR: ${BORINGSSL_ROOT_DIR}") message(STATUS "BORINGSSL_INCLUDE_DIR: ${BORINGSSL_INCLUDE_DIR}") message(STATUS "OPENSSL_VERSION: ${OPENSSL_VERSION}") message(STATUS "OPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR}") message(STATUS "OPENSSL_INCLUDE_DIR: ${OPENSSL_INCLUDE_DIR}") message(STATUS "OPENSSL_LIBRARIES: ${OPENSSL_LIBRARIES}") if (PAM_FOUND) message(STATUS "PAM_LIBRARY: ${PAM_LIBRARY}") message(STATUS "PAM_INCLUDE_DIR: ${PAM_INCLUDE_DIR}") endif() message(STATUS "BUILD_COMPRESSION: ${BUILD_COMPRESSION}") if (BUILD_COMPRESSION) message(STATUS "ZSTD_INCLUDE_DIR: ${ZSTD_INCLUDE_DIR}") message(STATUS "ZSTD_LIBRARY: ${ZSTD_LIBRARY}") message(STATUS "ZLIB_INCLUDE_DIRS: ${ZLIB_INCLUDE_DIRS}") message(STATUS "ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}") endif() message(STATUS "LDAP_SUPPORT: ${LDAP_FOUND}") if (LDAP_FOUND) message(STATUS "LDAP_LIBRARY: ${LDAP_LIBRARY}") message(STATUS "LDAP_INCLUDE_DIR: ${LDAP_INCLUDE_DIR}") endif() message(STATUS "SYSTEMD_FOUND: ${SYSTEMD_FOUND}") if (SYSTEMD_FOUND) message(STATUS "SYSTEMD_LIBRARY: ${SYSTEMD_LIBRARY}") message(STATUS "SYSTEMD_INCLUDE_DIR: ${SYSTEMD_INCLUDE_DIR}") endif() add_subdirectory(sources) set(OD_INSTALL_PREFIX "/usr/bin/") install(TARGETS odyssey DESTINATION "${OD_INSTALL_PREFIX}") include(Packaging) odyssey-1.6.0-rc1/LICENSE000066400000000000000000000026641526064202200147140ustar00rootroot00000000000000 Copyright (C) 2018 YANDEX LLC Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. odyssey-1.6.0-rc1/Makefile000066400000000000000000000266571526064202200153570ustar00rootroot00000000000000BUILD_TEST_DIR=build BUILD_REL_DIR=build BUILD_TEST_ASAN_DIR=build BUILD_TEST_TSAN_DIR=build FMT_BIN:=clang-format-18 CMAKE_BIN:=cmake SKIP_CLEANUP_DOCKER:= CMAKE_FLAGS:=-DCC_FLAGS="" BUILD_TYPE=Release DEV_CONF=./config-examples/odyssey-dev.conf ODYSSEY_BUILD_TYPE ?= build_release ODYSSEY_TEST_CODENAME ?= noble ODYSSEY_TEST_DEBIAN_DISTRO ?= bookworm ODYSSEY_TEST_TARGET_PLATFORM ?= linux/$(shell uname -m) ODYSSEY_ORACLELINUX_VERSION ?= 8 ODYSSEY_CC ?= gcc PROTO_TEST_TAG ?= all CONCURRENCY:=1 CURRENT_USER_UID_GID:=$(shell id -u):$(shell id -g) OS:=$(shell uname -s) ifeq ($(OS), Linux) CONCURRENCY:=$(shell nproc) endif ifeq ($(OS), Darwin) CONCURRENCY:=$(shell sysctl -n hw.logicalcpu) CMAKE_FLAGS += -DOPENSSL_ROOT_DIR=$(shell brew --prefix openssl@3 2>/dev/null) -DCMAKE_PREFIX_PATH=$(shell brew --prefix 2>/dev/null) CMAKE_FLAGS += -DBISON_EXECUTABLE=$(shell brew --prefix bison 2>/dev/null)/bin/bison CMAKE_FLAGS += -DFLEX_EXECUTABLE=$(shell FLEX_BREW=$(shell brew --prefix flex 2>/dev/null)/bin/flex; if [ -x "$$FLEX_BREW" ]; then echo "$$FLEX_BREW"; else which flex; fi) endif ifeq ($(OS), Darwin) STAT_OWNER_CMD := stat -f "%u:%g" . else STAT_OWNER_CMD := stat -c "%u:%g" . endif .PHONY: clean apply_fmt clean: rm -fr $(BUILD_REL_DIR) rm -fr $(BUILD_TEST_DIR) rm -fr $(BUILD_TEST_ASAN_DIR) local_build: clean +$(CMAKE_BIN) -B$(BUILD_TEST_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CMAKE_FLAGS) +make -C$(BUILD_TEST_DIR) -j$(CONCURRENCY) local_run: $(BUILD_TEST_DIR)/sources/odyssey $(DEV_CONF) console_run: $(BUILD_TEST_DIR)/sources/odyssey $(DEV_CONF) --console --log_to_stdout # --verbose check-format: docker build -f docker/format/Dockerfile --tag=odyssey/clang-format-runner . docker run -v .:/odyssey:ro odyssey/clang-format-runner modules sources format: docker build -f docker/format/Dockerfile --tag=odyssey/clang-format-runner . docker run --user=`$(STAT_OWNER_CMD)` -v .:/odyssey:rw odyssey/clang-format-runner -i modules sources build_images: docker build -f docker/quickstart/Dockerfile . --tag=odyssey build_asan: mkdir -p $(BUILD_TEST_ASAN_DIR) cd $(BUILD_TEST_ASAN_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=ASAN $(CMAKE_FLAGS) && make -j$(CONCURRENCY) # setarch `uname -m` -R ./odyssey ... build_tsan: mkdir -p $(BUILD_TEST_TSAN_DIR) cd $(BUILD_TEST_TSAN_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=TSAN $(CMAKE_FLAGS) && make -j$(CONCURRENCY) build_release: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=Release $(CMAKE_FLAGS) && make -j$(CONCURRENCY) build_relwithdbginfo: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=RelWithDbgInfo && make -j$(CONCURRENCY) build_relmemprof: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=RelWithDbgInfo -DMM_MEM_PROF=ON && make -j$(CONCURRENCY) build_reltcmalloc: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=Release -DUSE_TCMALLOC=ON && make -j$(CONCURRENCY) build_reltcmalloc_prof: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=Release -DUSE_TCMALLOC_PROFILE=ON && make -j$(CONCURRENCY) build_reldbgtcmalloc_prof: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=RelWithDbgInfo -DUSE_TCMALLOC_PROFILE=ON && make -j$(CONCURRENCY) build_reldbgtcmalloc: mkdir -p $(BUILD_REL_DIR) cd $(BUILD_REL_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=RelWithDbgInfo -DUSE_TCMALLOC=ON && make -j$(CONCURRENCY) build_dbg: mkdir -p $(BUILD_TEST_DIR) cd $(BUILD_TEST_DIR) && $(CMAKE_BIN) .. -DCMAKE_BUILD_TYPE=Debug && make -j$(CONCURRENCY) gdb: gdb --args ./build/sources/odyssey $(DEV_CONF) --console --log_to_stdout # --verbose lldb: lldb -- ./build/sources/odyssey $(DEV_CONF) --console --log_to_stdout submit-cov: mkdir cov-build && cd cov-build $(COV-BIN-PATH)/cov-build --dir cov-int make -j 4 && tar czvf odyssey.tgz cov-int && curl --form token=$(COV_TOKEN) --form email=$(COV_ISSUER) --form file=@./odyssey.tgz --form version="2" --form description="scalable potgresql connection pooler" https://scan.coverity.com/builds\?project\=yandex%2Fodyssey cpack-deb: build_release cd $(BUILD_REL_DIR) && cpack -G DEB cpack-deb-debug: build_dbg cd $(BUILD_REL_DIR) && cpack -G DEB cpack-rpm: build_release cd $(BUILD_REL_DIR) && cpack -G RPM cpack-rpm-debug: build_dbg cd $(BUILD_REL_DIR) && cpack -G RPM package-bionic: mkdir -p build ./docker/dpkg/runner.sh -c bionic -o build package-jammy: mkdir -p build ./docker/dpkg/runner.sh -c jammy -o build install: install -D build/sources/odyssey $(DESTDIR)/usr/bin/odyssey quickstart: build_images docker run -d \ --rm \ --name "odyssey" \ -p "6432:6432" \ -v ./docker/quickstart/config.conf:/etc/odyssey/odyssey.conf \ odyssey dev_run: format local_build console_run start-test-env-release: docker compose -f ./test/functional/docker-compose.yml down || true ODYSSEY_FUNCTIONAL_BUILD_TYPE=build_release \ ODYSSEY_TEST_TARGET=dev-env \ ODYSSEY_CC="$(ODYSSEY_CC)" \ docker compose -f ./test/functional/docker-compose.yml up --force-recreate --build -d --remove-orphans start-test-env-dbg: docker compose -f ./test/functional/docker-compose.yml down || true ODYSSEY_FUNCTIONAL_BUILD_TYPE=build_dbg \ ODYSSEY_TEST_TARGET=dev-env \ ODYSSEY_CC="$(ODYSSEY_CC)" \ docker compose -f ./test/functional/docker-compose.yml up --force-recreate --build -d --remove-orphans start-test-env-asan: docker compose -f ./test/functional/docker-compose.yml down || true ODYSSEY_FUNCTIONAL_BUILD_TYPE=build_asan \ ODYSSEY_TEST_TARGET=dev-env \ ODYSSEY_CC="$(ODYSSEY_CC)" \ docker compose -f ./test/functional/docker-compose.yml up --force-recreate --build -d --remove-orphans quickstart_test: docker build -f docker/quickstart/Dockerfile . --tag=odyssey docker compose -f ./test/quickstart/docker-compose.yml up --exit-code-from tester --force-recreate --build --remove-orphans prometheus-legacy-test: docker compose -f ./test/prometheus-legacy/docker-compose.yml down || true ODYSSEY_PROM_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) \ docker compose -f ./test/prometheus-legacy/docker-compose.yml up --exit-code-from odyssey --force-recreate --build --remove-orphans soft-oom-test: build_images docker compose -f ./test/oom/docker-compose.yml down || true docker compose -f ./test/oom/docker-compose.yml up --exit-code-from runner --force-recreate --build --remove-orphans prom-exporter-test: build_images docker compose -f ./test/prom-exporter/docker-compose.yml up --exit-code-from tester --force-recreate --build --remove-orphans async-notice-test: build_images docker compose -f ./test/async-notice/docker-compose.yml up --exit-code-from tester --force-recreate --build --remove-orphans proto-test: ODYSSEY_PROTO_TEST_TAG=$(PROTO_TEST_TAG) \ ODYSSEY_CC="$(ODYSSEY_CC)" \ ODYSSEY_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) \ ODYSSEY_PROTO_TEST_SPQR_REF=$(shell git ls-remote https://github.com/pg-sharding/spqr HEAD | cut -f1) \ docker compose -f ./test/proto/docker-compose.yml up --exit-code-from tester --force-recreate --build --remove-orphans functional-test: ODYSSEY_FUNCTIONAL_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) \ ODYSSEY_TEST_TARGET=functional-entrypoint \ ODYSSEY_FUNCTIONAL_TESTS_SELECTOR="$(ODYSSEY_TEST_SELECTOR)" \ ODYSSEY_CC="$(ODYSSEY_CC)" \ docker compose -f ./test/functional/docker-compose.yml up --exit-code-from odyssey --build --remove-orphans jemalloc-test: docker compose -f ./test/jeamalloc/docker-compose.yml down || true ODYSSEY_JEMALLOC_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) \ docker compose -f ./test/jemalloc/docker-compose.yml up --exit-code-from runner --build --remove-orphans stress-tests: docker compose -f ./test/stress/docker-compose.yml down || true ODYSSEY_STRESS_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) \ ODYSSEY_STRESS_TEST_TARGET=stress-entrypoint \ docker compose -f ./test/stress/docker-compose.yml up --exit-code-from runner --build --remove-orphans stress-tests-dev-env: docker compose -f ./test/stress/docker-compose.yml down || true ODYSSEY_STRESS_BUILD_TYPE=build_release \ ODYSSEY_STRESS_TEST_TARGET=dev-env \ docker compose -f ./test/stress/docker-compose.yml up --force-recreate --build -d --remove-orphans stress-tests-dev-env-dbg: docker compose -f ./test/stress/docker-compose.yml down || true ODYSSEY_STRESS_BUILD_TYPE=build_dbg \ ODYSSEY_STRESS_TEST_TARGET=dev-env \ docker compose -f ./test/stress/docker-compose.yml up --force-recreate --build -d --remove-orphans replication_test: build_images docker compose -f ./test/replication/docker-compose.yml up --exit-code-from tester --build --remove-orphans --force-recreate jdbc_test: build_images docker compose -f ./test/drivers/jdbc/docker-compose.yml up --exit-code-from regress_test --build --remove-orphans --force-recreate userver_test: build_images docker compose -f ./test/drivers/userver/docker-compose.yml up --build --abort-on-container-exit --exit-code-from userver_test --remove-orphans # --force-recreate regress_test: docker build -f ./test/pg_regress/Dockerfile \ --build-arg build_type=$(ODYSSEY_BUILD_TYPE) \ --build-arg odyssey_cc=$(ODYSSEY_CC) \ --tag=odyssey/regress-tester . docker run odyssey/regress-tester scram_passthrough_test: docker build -f ./test/scram-passthrough/Dockerfile \ --build-arg build_type=$(ODYSSEY_BUILD_TYPE) \ --build-arg odyssey_cc=$(ODYSSEY_CC) \ --tag=odyssey/scram-passthrough-tester . docker run odyssey/scram-passthrough-tester ci-unittests: docker build \ --platform $(ODYSSEY_TEST_TARGET_PLATFORM) \ -f ./test/unit/Dockerfile \ --build-arg build_type=$(ODYSSEY_BUILD_TYPE) \ --build-arg odyssey_cc=$(ODYSSEY_CC) \ --tag=odyssey/unit-test-runner . docker run odyssey/unit-test-runner ci-build-check-ubuntu: docker build \ --platform $(ODYSSEY_TEST_TARGET_PLATFORM) \ -f test/build-test/Dockerfile.ubuntu \ --build-arg codename=$(ODYSSEY_TEST_CODENAME) \ --tag=odyssey/$(ODYSSEY_TEST_CODENAME)-builder-$(ODYSSEY_TEST_TARGET_PLATFORM) . docker run -e ODYSSEY_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) odyssey/$(ODYSSEY_TEST_CODENAME)-builder-$(ODYSSEY_TEST_TARGET_PLATFORM) ci-build-check-debian: docker build \ --platform $(ODYSSEY_TEST_TARGET_PLATFORM) \ -f test/build-test/Dockerfile.debian \ --build-arg codename=$(ODYSSEY_TEST_DEBIAN_DISTRO) \ --tag=odyssey/$(ODYSSEY_TEST_DEBIAN_DISTRO)-builder-$(ODYSSEY_TEST_TARGET_PLATFORM) . docker run -e ODYSSEY_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) odyssey/$(ODYSSEY_TEST_DEBIAN_DISTRO)-builder-$(ODYSSEY_TEST_TARGET_PLATFORM) ci-build-check-fedora: docker build \ -f test/build-test/Dockerfile.fedora \ --tag=odyssey/fedora-img . ci-build-check-oracle-linux: docker build \ -f test/build-test/Dockerfile.oraclelinux \ --build-arg version=$(ODYSSEY_ORACLELINUX_VERSION) \ --tag=odyssey/oraclelinux-$(ODYSSEY_ORACLELINUX_VERSION)-builder . docker run -e ODYSSEY_BUILD_TYPE=$(ODYSSEY_BUILD_TYPE) odyssey/oraclelinux-$(ODYSSEY_ORACLELINUX_VERSION)-builder ci-build-check-macos: local_build build-docs-web: docker build -f docs/Dockerfile --tag=odyssey/docs-builder . docker run --user="$(CURRENT_USER_UID_GID)" -v .:/odyssey:rw odyssey/docs-builder serve-docs: build-docs-web docker stop odyssey_docs_web || true docker run \ -it --rm \ --name odyssey_docs_web \ -p "80:80" -p "443:443" \ -v "${PWD}/site:/usr/share/nginx/html:ro" \ -v "${PWD}/docs/nginx.conf:/etc/nginx/nginx.conf:ro" \ -v "${PWD}/certificate.pem":/etc/certificate.pem:ro \ -d nginx:alpine debian-build-package: dpkg-buildpackage -us -uc debian-lintian-check: lintian -I -E --pedantic ../odyssey_*_amd64.deb odyssey-1.6.0-rc1/README.md000066400000000000000000000065121526064202200151620ustar00rootroot00000000000000



## Odyssey AI-ready advanced multi-threaded PostgreSQL connection pooler and request router. #### Project status Odyssey is production-ready, it is being used in large production setups. We appreciate any kind of feedback and contribution to the project. Coverity Scan Build Status ### Design goals and main features #### Multi-threaded processing Odyssey can significantly scale processing performance by specifying a number of additional worker threads. Each worker thread is responsible for authentication and proxying client-to-server and server-to-client requests. All worker threads are sharing global server connection pools. Multi-threaded design plays important role in `SSL/TLS` performance. #### Advanced transactional pooling Odyssey tracks current transaction state and in case of unexpected client disconnection can emit automatic `Cancel` connection and do `Rollback` of abandoned transaction, before putting server connection back to the server pool for reuse. Additionally, last server connection owner client is remembered to reduce a need for setting up client options on each client-to-server assignment. #### Better pooling control Odyssey allows to define connection pools as a pair of `Database` and `User`. Each defined pool can have separate authentication, pooling mode and limits settings. #### Authentication Odyssey has full-featured `SSL/TLS` support and common authentication methods like: `md5` and `clear text` both for client and server authentication. Odyssey supports PAM & LDAP authentication, this methods operates similarly to `clear text` auth except that it uses PAM/LDAP to validate user name/password pairs. PAM optionally checks the connected remote host name or IP address. Additionally it allows to block each pool user separately. #### Logging Odyssey generates universally unique identifiers `uuid` for client and server connections. Any log events and client error responses include the id, which then can be used to uniquely identify client and track actions. Odyssey can save log events into log file and using system logger. #### Build instructions Currently Odyssey runs only on Linux. Supported platforms are x86/x86_64. To build you will need in ubuntu distros: * cmake >= 3.12.4 * gcc >= 4.6 * openssl * flex * bison Optional dependencies: * libsystemd-dev (for systemd notify support) And for fedora-based distros: * openssl-devel * flex * bison * systemd-devel (optional, for systemd notify) > **Note (since 1.5.3):** the config parser is now generated from a Flex/Bison > grammar at build time. `flex` and `bison` must be installed before running > cmake. For Mac-OS you will need: ```sh git clone git://github.com/yandex/odyssey.git cd odyssey make local_build ``` Adapt odyssey-dev.conf then: ```sh make local_run ``` Alternatively: ```sh make console_run ``` ### Documentation See [docs](docs/) dir with more documentation info. You can serve docs locally by `make serve_docs`. You can also read about Odyssey on our [website](https://pg-odyssey.tech/). ### Support We have a [Telegram chat](https://t.me/+ecwqGEkVgXg2OTQy) to discuss Odyssey usage and development. odyssey-1.6.0-rc1/VERSION000066400000000000000000000000121526064202200147400ustar00rootroot000000000000001.6.0-rc1 odyssey-1.6.0-rc1/benchmarks/000077500000000000000000000000001526064202200160145ustar00rootroot00000000000000odyssey-1.6.0-rc1/benchmarks/client_max_routing/000077500000000000000000000000001526064202200217065ustar00rootroot00000000000000odyssey-1.6.0-rc1/benchmarks/client_max_routing/go.mod000066400000000000000000000002231526064202200230110ustar00rootroot00000000000000module catchuploadtest go 1.26.0 require ( github.com/lib/pq v1.12.3 golang.org/x/term v0.46.0 ) require golang.org/x/sys v0.48.0 // indirect odyssey-1.6.0-rc1/benchmarks/client_max_routing/go.sum000066400000000000000000000007171526064202200230460ustar00rootroot00000000000000github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= odyssey-1.6.0-rc1/benchmarks/client_max_routing/main.go000066400000000000000000000170541526064202200231700ustar00rootroot00000000000000package main import ( "context" "database/sql" "encoding/csv" "flag" "fmt" "os" "strconv" "strings" "sync" "sync/atomic" "time" _ "github.com/lib/pq" "golang.org/x/term" ) type IntArrayFlags []int func newIntArrayFlags(defaults []int) *IntArrayFlags { i := IntArrayFlags(defaults) return &i } func (i *IntArrayFlags) String() string { return fmt.Sprintf("%v", *i) } func (i *IntArrayFlags) Set(value string) error { *i = nil for _, n := range strings.FieldsFunc(value, func(r rune) bool { return r == ',' || r == ' ' }) { v, err := strconv.Atoi(n) if err != nil { return err } *i = append(*i, v) } return nil } type DurationArrayFlags []time.Duration func newDurationArrayFlags(defaults []time.Duration) *DurationArrayFlags { d := DurationArrayFlags(defaults) return &d } func (d *DurationArrayFlags) String() string { return fmt.Sprintf("%v", *d) } func (d *DurationArrayFlags) Set(value string) error { *d = nil for _, n := range strings.FieldsFunc(value, func(r rune) bool { return r == ',' || r == ' ' }) { v, err := time.ParseDuration(n) if err != nil { return err } *d = append(*d, v) } return nil } type Config struct { ConnectionString string BenchmarkDuration time.Duration ClientParallels []int ConnectTimeouts []time.Duration SelectTimeouts []time.Duration PauseDuration time.Duration OutputFile string } type Result struct { Connections int64 Selects int64 Errors int64 Clients int TotalTime time.Duration ConnectTimeout time.Duration SelectTimeout time.Duration } var ( connectionsCounter int64 = 0 selectCounter int64 = 0 errorsCounter int64 = 0 ) func doConnect(cfg *Config, connectTimeout time.Duration) { db, err := sql.Open("postgres", cfg.ConnectionString) if err != nil { fmt.Printf("connect error: %v\n", err) atomic.AddInt64(&errorsCounter, 1) return } defer db.Close() ctx, cancel := context.WithTimeout(context.Background(), connectTimeout) defer cancel() err = db.PingContext(ctx) if err != nil { fmt.Printf("ping error: %v\n", err) atomic.AddInt64(&errorsCounter, 1) return } atomic.AddInt64(&connectionsCounter, 1) } func doConnectInf(ctx context.Context, cfg *Config, connectTimeout time.Duration, wg *sync.WaitGroup, syncStart *sync.WaitGroup) { syncStart.Wait() for { select { case <-ctx.Done(): wg.Done() return default: doConnect(cfg, connectTimeout) } } } func doSelect(db *sql.DB, timeout time.Duration) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() var val int err := db.QueryRowContext(ctx, "select 1 + 2").Scan(&val) if err != nil { fmt.Printf("select error: %v\n", err) atomic.AddInt64(&errorsCounter, 1) return } if val != 3 { panic("wrong value") } atomic.AddInt64(&selectCounter, 1) } func doSelectInf(ctx context.Context, cfg *Config, selectTimeout time.Duration, wg *sync.WaitGroup) { db, err := sql.Open("postgres", cfg.ConnectionString) noerror(err) defer db.Close() for { select { case <-ctx.Done(): wg.Done() return default: doSelect(db, selectTimeout) } } } func doMeasure(cfg *Config, nparallel int, connectTimeout time.Duration, selectTimeout time.Duration) *Result { atomic.StoreInt64(&connectionsCounter, 0) atomic.StoreInt64(&selectCounter, 0) atomic.StoreInt64(&errorsCounter, 0) ctx, cancel := context.WithTimeout(context.Background(), cfg.BenchmarkDuration) defer cancel() wg := &sync.WaitGroup{} syncStart := &sync.WaitGroup{} syncStart.Add(1) for range nparallel { wg.Add(1) go doSelectInf(ctx, cfg, selectTimeout, wg) } for range nparallel { wg.Add(1) go doConnectInf(ctx, cfg, connectTimeout, wg, syncStart) } startTime := time.Now() syncStart.Done() wg.Wait() totalTime := time.Since(startTime) return &Result{ Connections: atomic.LoadInt64(&connectionsCounter), Selects: atomic.LoadInt64(&selectCounter), Errors: atomic.LoadInt64(&errorsCounter), TotalTime: totalTime, Clients: nparallel, ConnectTimeout: connectTimeout, SelectTimeout: selectTimeout, } } func printResultHeaderLine() { fmt.Printf("clients\ttime\tconn to\tc/s\ts/s\te/s\n") } func printResultLine(r *Result) { fmt.Printf("%d\t%v\t%v\t%v\t%f\t%f\t%f\n", r.Clients, r.TotalTime, r.ConnectTimeout, r.SelectTimeout, float64(r.Connections)/r.TotalTime.Seconds(), float64(r.Selects)/r.TotalTime.Seconds(), float64(r.Errors)/r.TotalTime.Seconds(), ) } func printResultConsole(results []*Result) { printResultHeaderLine() for _, r := range results { printResultLine(r) } } func printResultCsv(filename string, results []*Result) { f, err := os.Create(filename) noerror(err) defer f.Close() w := csv.NewWriter(f) defer w.Flush() w.Write([]string{"#", "clients", "time", "conn to", "select to", "c/s", "s/s", "e/s"}) for i, r := range results { err := w.Write([]string{ strconv.Itoa(i + 1), strconv.Itoa(r.Clients), r.TotalTime.String(), r.ConnectTimeout.String(), r.SelectTimeout.String(), fmt.Sprintf("%f", float64(r.Connections)/r.TotalTime.Seconds()), fmt.Sprintf("%f", float64(r.Selects)/r.TotalTime.Seconds()), fmt.Sprintf("%f", float64(r.Errors)/r.TotalTime.Seconds()), }) noerror(err) } fmt.Printf("Results saved to %s\n", filename) } func printResults(cfg *Config, results []*Result) { printResultConsole(results) printResultCsv(cfg.OutputFile, results) } func runBenchmarks(cfg *Config) []*Result { results := make([]*Result, 0, len(cfg.ClientParallels)) for _, np := range cfg.ClientParallels { for _, ct := range cfg.ConnectTimeouts { for _, st := range cfg.SelectTimeouts { r := doMeasure(cfg, np, ct, st) printResultLine(r) results = append(results, r) time.Sleep(cfg.PauseDuration) } } } return results } func readConfig() *Config { host := flag.String("host", "", "odyssey host") port := flag.Int("port", 6432, "odyssey port") user := flag.String("user", "user1", "user to connect") dbName := flag.String("db", "db1", "db to connect") sslRoot := flag.String("sslroot", "./root.crt", "ssl root.crt file path") benchDuration := flag.Duration("bench-duration", time.Second*5, "one benchmark run duration") pauseDuration := flag.Duration("pause-duration", time.Second*3, "pause between runs") outputFile := flag.String("output-file", "./result.csv", "result filename") clients := newIntArrayFlags([]int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}) flag.Var(clients, "clients", "numbers of parallel connecting clients") connectTimeouts := newDurationArrayFlags([]time.Duration{time.Second}) flag.Var(connectTimeouts, "connect-timeouts", "connect timeouts") selectTimeouts := newDurationArrayFlags([]time.Duration{time.Second}) flag.Var(selectTimeouts, "select-timeouts", "select timeouts") flag.Parse() if len(*host) == 0 { fmt.Printf("Error: the host parameter is empty\n") flag.Usage() return nil } fmt.Printf("%s's password (no echo):", *user) password, err := term.ReadPassword(0) noerror(err) return &Config{ ConnectionString: fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=verify-full sslrootcert=%s", *host, *port, *user, string(password), *dbName, *sslRoot), BenchmarkDuration: *benchDuration, ClientParallels: *clients, PauseDuration: *pauseDuration, OutputFile: *outputFile, ConnectTimeouts: *connectTimeouts, SelectTimeouts: *selectTimeouts, } } func main() { cfg := readConfig() if cfg == nil { return } results := runBenchmarks(cfg) printResults(cfg, results) } func noerror(err error) { if err != nil { panic(err) } } odyssey-1.6.0-rc1/cmake/000077500000000000000000000000001526064202200147575ustar00rootroot00000000000000odyssey-1.6.0-rc1/cmake/FindBoringSSL.cmake000066400000000000000000000035161526064202200203710ustar00rootroot00000000000000# - Try to find boringssl include dirs and libraries # # Usage of this module as follows: # # find_package(BORINGSSL) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # BORINGSSL_ROOT_DIR Set this variable to the root installation of # boringssl if the module has problems finding the # proper installation path. # # Variables defined by this module: # # BORINGSSL_FOUND System has boringssl, include and library dirs found # BORINGSSL_INCLUDE_DIR The boringssl include directories. # BORINGSSL_LIBRARIES The boringssl libraries. # BORINGSSL_CRYPTO_LIBRARY The boringssl crypto library. # BORINGSSL_SSL_LIBRARY The boringssl ssl library. find_path(BORINGSSL_ROOT_DIR NAMES include/openssl/ssl.h include/openssl/base.h include/openssl/hkdf.h HINTS ${BORINGSSL_ROOT_DIR}) find_path(BORINGSSL_INCLUDE_DIR NAMES openssl/ssl.h openssl/base.h openssl/hkdf.h HINTS ${BORINGSSL_ROOT_DIR}/include) find_library(BORINGSSL_SSL_LIBRARY NAMES libssl.a HINTS ${BORINGSSL_ROOT_DIR}/build/ssl) find_library(BORINGSSL_CRYPTO_LIBRARY NAMES libcrypto.a HINTS ${BORINGSSL_ROOT_DIR}/build/crypto) set(BORINGSSL_LIBRARIES ${BORINGSSL_SSL_LIBRARY} ${BORINGSSL_CRYPTO_LIBRARY} CACHE STRING "BoringSSL SSL and crypto libraries" FORCE) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(BORINGSSL DEFAULT_MSG BORINGSSL_LIBRARIES BORINGSSL_INCLUDE_DIR) mark_as_advanced( BORINGSSL_ROOT_DIR BORINGSSL_INCLUDE_DIR BORINGSSL_LIBRARIES BORINGSSL_CRYPTO_LIBRARY BORINGSSL_SSL_LIBRARY ) odyssey-1.6.0-rc1/cmake/FindLDAP.cmake000066400000000000000000000020071526064202200173010ustar00rootroot00000000000000# - Try to find the LDAP libraries # # LDAP_FOUND - ldap was successfully found # LDAP_INCLUDE_DIR - ldap include directory # LDAP_LIBRARIES - ldap libraries if (APPLE) # macOS ships a deprecated LDAP.framework whose ldap.h header is broken # when included directly (causes "include nested too deeply"). # Prefer OpenLDAP installed via Homebrew instead, and explicitly # disable framework-style lookup for this search. find_path(LDAP_INCLUDE_DIR NAMES ldap.h HINTS /opt/homebrew/opt/openldap/include /usr/local/opt/openldap/include NO_CMAKE_FIND_ROOT_PATH NO_DEFAULT_PATH ) find_library(LDAP_LIBRARY NAMES ldap HINTS /opt/homebrew/opt/openldap/lib /usr/local/opt/openldap/lib NO_CMAKE_FIND_ROOT_PATH NO_DEFAULT_PATH ) else() find_path(LDAP_INCLUDE_DIR NAMES ldap.h) find_library(LDAP_LIBRARY ldap) endif() find_package_handle_standard_args(LDAP REQUIRED_VARS LDAP_LIBRARY LDAP_INCLUDE_DIR) odyssey-1.6.0-rc1/cmake/FindPAM.cmake000066400000000000000000000005231526064202200171770ustar00rootroot00000000000000 # - Try to find the PAM libraries # # PAM_FOUND - pam was successfully found # PAM_INCLUDE_DIR - pam include directory # PAM_LIBRARIES - pam libraries find_path(PAM_INCLUDE_DIR NAMES pam_appl.h PATH_SUFFIXES security pam) find_library(PAM_LIBRARY pam) find_package_handle_standard_args(PAM REQUIRED_VARS PAM_LIBRARY PAM_INCLUDE_DIR) odyssey-1.6.0-rc1/cmake/FindProm.cmake000066400000000000000000000005111526064202200174740ustar00rootroot00000000000000# - Try to find the Prometheus C client library # # PROM_FOUND - prom was successfully found # PROM_INCLUDE_DIR - prom include directory # PROM_LIBRARY - prom library find_path(PROM_INCLUDE_DIR NAMES prom.h) find_library(PROM_LIBRARY prom) find_package_handle_standard_args(Prom REQUIRED_VARS PROM_LIBRARY PROM_INCLUDE_DIR) odyssey-1.6.0-rc1/cmake/FindPromhttp.cmake000066400000000000000000000005751526064202200204060ustar00rootroot00000000000000# - Try to find the Prometheus C client library # # PROMHTTP_FOUND - promhttp was successfully found # PROMHTTP_INCLUDE_DIR - promhttp include directory # PROMHTTP_LIBRARY - promhttp library find_path(PROMHTTP_INCLUDE_DIR NAMES promhttp.h) find_library(PROMHTTP_LIBRARY promhttp) find_package_handle_standard_args(Promhttp REQUIRED_VARS PROMHTTP_LIBRARY PROMHTTP_INCLUDE_DIR) odyssey-1.6.0-rc1/cmake/FindZstd.cmake000066400000000000000000000022621526064202200175100ustar00rootroot00000000000000# Copyright (c) Facebook, Inc. and its affiliates. # # 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. # # - Try to find Facebook zstd library # This will define # ZSTD_FOUND # ZSTD_INCLUDE_DIR # ZSTD_LIBRARY # find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) find_library(ZSTD_LIBRARY_DEBUG NAMES zstdd zstd_staticd) find_library(ZSTD_LIBRARY_RELEASE NAMES zstd zstd_static) include(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(ZSTD) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( ZSTD DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR ) if (ZSTD_FOUND) message(STATUS "Found Zstd: ${ZSTD_LIBRARY}") endif() mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)odyssey-1.6.0-rc1/cmake/Packaging.cmake000066400000000000000000000051201526064202200176430ustar00rootroot00000000000000set(CPACK_GENERATOR TGZ DEB RPM) set(CPACK_PACKAGE_NAME "${PROJECT_NAME}" CACHE STRING "The resulting package name" ) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Scalable PostgreSQL connection pooler." CACHE STRING "Package description for the package metadata" ) set(CPACK_PACKAGE_VENDOR "YANDEX LLC") set(CPACK_VERBATIM_VARIABLES YES) set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/packages") set(CPACK_PACKAGE_CONTACT "Roman Khapov ") set(CPACK_PACKAGE_VERSION "${OD_VERSION}") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}") set(CPACK_SOURCE_IGNORE_FILES .git/ .github/ .vscode/ build ${CMAKE_BINARY_DIR}/ ${PROJECT_BINARY_DIR}/) set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/yandex/odyssey") set( CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) # Generate and insert changelog set(CHANGELOG_PATH "${CMAKE_SOURCE_DIR}/debian/changelog") set(CHANGELOG_GZ_PATH "${CMAKE_CURRENT_BINARY_DIR}/changelog.Debian.gz") include(GNUInstallDirs) add_custom_command( OUTPUT "${CHANGELOG_GZ_PATH}" COMMAND gzip -cn9 "${CHANGELOG_PATH}" > "${CHANGELOG_GZ_PATH}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS "${CHANGELOG_PATH}" COMMENT "Compressing changelog" ) add_custom_target(changelog ALL DEPENDS "${CHANGELOG_GZ_PATH}") install(FILES "${CHANGELOG_GZ_PATH}" DESTINATION "${CMAKE_INSTALL_DOCDIR}") # ===================== DEB ===================== set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Advanced multi-threaded PostgreSQL connection pooler and request router." CACHE STRING "Package description for the package metadata" ) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Roman Khapov ") set(CPACK_DEBIAN_PACKAGE_DEPENDS "make, cmake, libssl-dev (>= 1.0.1), libpq-dev, libpam-dev, postgresql-server-dev-all, libzstd-dev, zlib1g-dev") set(CPACK_DEBIAN_PACKAGE_SECTION "database") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_CONFLICTS "pgbouncer") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEB_COMPONENT_INSTALL YES) set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON) # ===================== RPM ===================== set(CPACK_RPM_DEBUGINFO_PACKAGE YES) # set(CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX "/usr/lib/") # set(CPACK_BUILD_SOURCE_DIRS "${CMAKE_SOURCE_DIR}"/sources) include(CPack) odyssey-1.6.0-rc1/config-examples/000077500000000000000000000000001526064202200167605ustar00rootroot00000000000000odyssey-1.6.0-rc1/config-examples/odyssey-aq.conf000066400000000000000000000033321526064202200217260ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session yes log_query yes log_stats yes stats_interval 60 log_general_stats_prom yes log_route_stats_prom no promhttp_server_port 7777 workers "auto" resolvers 1 readahead 8192 cache_coroutine 0 coroutine_stack_size 16 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "[localhost]:5432,localhost" port 5550 } database default { user default { authentication "scram-sha-256" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "admin" auth_query_db "postgres" storage_password "odyssey" storage_user "odyssey" storage "postgres_server" pool "session" pool_size 0 pool_timeout 0 pool_ttl 1201 pool_discard no pool_cancel yes pool_rollback yes # seconds pool_client_idle_timeout 20 # seconds pool_idle_in_transaction_timeout 20 client_fwd_error yes application_name_add_host yes server_lifetime 1901 log_debug no # add this options to backend-startup package backend_startup_options { "_pq_.service_auth_role" "odyssey" } quantiles "0.99,0.95,0.5" client_max 107 } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yesodyssey-1.6.0-rc1/config-examples/odyssey-dev-ldap.conf000066400000000000000000000132611526064202200230230ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session yes log_query yes log_stats yes stats_interval 60 log_stats_general_prom yes workers "auto" resolvers 1 readahead 8192 cache_coroutine 0 coroutine_stack_size 16 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 compression yes } storage "postgres_server" { type "remote" host "localhost" port 5432 } # ldapserver=localhost ldapbinddn="cn=admin,dc=example,dc=org" ldapbasedn="dc=example,dc=org" ldapbindpasswd="admin" ldapsearchfilter="(uid=$username)" ldap_endpoint "ldap1" { ldapscheme "ldap" ldapbasedn "dc=example,dc=org" ldapbinddn "cn=admin,dc=example,dc=org" ldapbindpasswd "admin" # ldapsearchfilter "(memberOf=cn=test-db-for-ldap,cn=groups,dc=example,dc=org)" ldapsearchattribute "gecos" ldapserver "localhost" ldapport 389 } ldap_endpoint "ldap_unreachable" { ldapscheme "ldap" ldapbasedn "dc=example,dc=org" ldapbinddn "cn=admin,dc=example,dc=org" ldapbindpasswd "admin" ldapsearchattribute "gecos" ldapserver "localhost" ldapport 38 } database default { user default { authentication "none" storage "postgres_server" pool "session" pool_size 0 pool_timeout 0 pool_ttl 1201 pool_discard no pool_cancel yes pool_rollback yes # seconds pool_client_idle_timeout 20 # seconds pool_idle_in_transaction_timeout 20 client_fwd_error yes application_name_add_host yes server_lifetime 1901 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres2" { user "user3" { authentication "clear_text" storage "postgres_server" pool "session" pool_size 10 ldap_pool_size 10 ldap_pool_timeout 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap_unreachable" quantiles "0.99,0.95,0.5" client_max 107 } user "user1" { authentication "clear_text" storage "postgres_server" # storage_password "lolol" pool "session" ldap_pool_size 1 ldap_pool_timeout 0 pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" password_passthrough yes quantiles "0.99,0.95,0.5" client_max 107 } user "user2" { authentication "clear_text" storage "postgres_server" storage_password "1" pool "session" ldap_pool_size 1 ldap_pool_timeout 0 pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" quantiles "0.99,0.95,0.5" client_max 107 } user default { authentication "none" storage "postgres_server" pool "session" } } database "postgres" { user "user1" { authentication "clear_text" storage "postgres_server" # storage_password "1" pool "session" pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" quantiles "0.99,0.95,0.5" client_max 107 } user "user2" { authentication "clear_text" storage "postgres_server" storage_password "1" pool "session" pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" quantiles "0.99,0.95,0.5" client_max 107 } user "userstmt" { authentication "none" storage "postgres_server" storage_password "1" pool "statement" pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" quantiles "0.99,0.95,0.5" client_max 107 } user default { authentication "none" storage "postgres_server" pool "session" } } storage "local" { type "local" } database "console" { user "stat" { authentication "none" role "stat" pool "session" storage "local" } user "admin" { authentication "none" role "stat" pool "session" storage "local" } user "rogue" { authentication "none" role "notallow" pool "session" storage "local" } user default { authentication "none" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yes odyssey-1.6.0-rc1/config-examples/odyssey-dev-with-watchdog.conf000066400000000000000000000067361526064202200246650ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session yes log_query yes log_stats yes stats_interval 60 log_general_stats_prom yes workers "auto" resolvers 1 readahead 8192 cache_coroutine 0 coroutine_stack_size 16 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 compression yes } storage "postgres_server" { type "remote" host "localhost" port 5432 watchdog { authentication "none" # makes no sense storage "postgres_server" storage_db "postgres" storage_user "lag_usr" pool_routing "internal" pool "transaction" # 1 for cron & 9 for console db queries pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM NOW())) - 100" # watchdog_lag_query "SELECT 1" watchdog_lag_interval 10 } } database default { user default { authentication "none" storage "postgres_server" pool "session" pool_size 0 pool_timeout 0 pool_ttl 1201 pool_discard no pool_cancel yes pool_rollback yes # seconds pool_client_idle_timeout 20 # seconds pool_idle_in_transaction_timeout 20 client_fwd_error yes application_name_add_host yes server_lifetime 1901 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres" { user "user_lag" { authentication "clear_text" password "passwd" storage "postgres_server" pool "session" pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard yes pool_cancel yes pool_rollback yes catchup_timeout 10 catchup_checks 10 client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } user "user_aq_internal_pooling" { authentication "none" storage "postgres_server" pool "session" storage_user "reshke" storage_db "postgres" log_debug yes pool_discard yes pool_routing "internal" quantiles "0.99,0.95,0.5" client_max 107 } user "user_aq" { authentication "clear_text" # password "passwd" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "user_aq_internal_pooling" auth_query_db "postgres" storage "postgres_server" # storage_password "1" pool "session" pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } user "userstmt" { authentication "none" storage "postgres_server" storage_password "1" pool "statement" pool_size 1 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } user default { authentication "none" storage "postgres_server" pool "session" } } storage "local" { type "local" } database "console" { user default { authentication "none" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yes odyssey-1.6.0-rc1/config-examples/odyssey-dev.conf000066400000000000000000000032241526064202200221030ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 # cpu_affinity yes workers 1 resolvers 1 readahead 4096 cache_coroutine 0 coroutine_stack_size 16 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "localhost:5432" endpoints_status_poll_interval 100 } database default { user default { authentication "none" # password "md588cb17a149f659b9a78ec4a33cbb3c7f" storage "postgres_server" pool "transaction" pool_size 32 # auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" # auth_query_db "postgres" # auth_query_user "reshke" # storage_password "reshke" pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes # seconds pool_client_idle_timeout 20000 # seconds pool_idle_in_transaction_timeout 20000 client_fwd_error yes application_name_add_host no server_lifetime 1901 log_debug no # quantiles "0.99,0.95,0.5" client_max 200000 } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" virtual_processing yes accept_rate_limit 10 graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yes odyssey-1.6.0-rc1/debian/000077500000000000000000000000001526064202200151215ustar00rootroot00000000000000odyssey-1.6.0-rc1/debian/changelog000066400000000000000000000074551526064202200170060ustar00rootroot00000000000000odyssey (1.6.0-rc1-1) unstable; urgency=medium * Fix SSL_CTX cache (#1627) * fix: require a full-length SCRAM client proof (#1616) * Add virtual "odyssey.pooling_mode" GUC. (#1607) * Add cancel_rate_limit (#1603) * Forcely close OOMed connections (#1604) * Add autoconf support (#1598) * Introduce new counters for clients waiting for a route (#1507) * Add odyssey.opportunistic_acquire GUC (#1583) * Track UNLISTEN executed by extended proto * Unpin backend on UNLISTEN by simple query (#1573) * Rate limit client connections accept (#1550) * Add virtual odyssey.version GUC (#1547) * listen-level balancing section override (#1537) * listen-level balancing_method override (#1535) * ref counters for global pstmts map (#1515) * support FreeBSD build (#1517) * correct support for "allow" for backends (#1514) * keepalive parameters in rule config (#1513) * Add support for "prefer" mode of tls (#1511) * add listen-level storage failover (#1502) * add portal tacking on client side (#1494) * az_aware option (#1479) * OpenSSL 4 compatibility (#1466) * simple mac os support (#1467) * scram-passthrough impl (#1463) -- Yandex Database Team Wed, 30 Sep 2026 17:02:26 +0500 odyssey (1.5.1-rc9-1) unstable; urgency=medium * fix logger shutdown * refactor of group checker -- Yandex Database Team Mon, 25 May 2026 22:26:13 +0500 odyssey (1.5.1-rc8-1) unstable; urgency=medium * protocol support fixes * async logging improvement -- Yandex Database Team Thu, 07 May 2026 03:19:52 +0000 odyssey (1.5.1-rc7-1) unstable; urgency=medium * bug fixes -- Yandex Database Team Mon, 20 Apr 2026 10:26:47 +0000 odyssey (1.5.1-rc6-1) unstable; urgency=medium * avg_wait_time/total_wait_time support * bug fixes -- Yandex Database Team Sun, 19 Apr 2026 10:47:00 +0000 odyssey (1.5.1-rc5-1) unstable; urgency=medium * support DEALLOCATE ALL for reserving pstmts * poll server on attach * bug fixes -- Yandex Database Team Wed, 15 Apr 2026 04:18:07 +0000 odyssey (1.5.1-rc4-1) unstable; urgency=medium * relay rework, to fix bugs with xproto -- Yandex Database Team Fri, 10 Apr 2026 03:45:23 +0000 odyssey (1.5.1-rc3-1) unstable; urgency=medium * do not accumulate too large iov * fix route_wait race -- Yandex Database Team Fri, 20 Feb 2026 06:02:53 +0000 odyssey (1.5.1-rc2-1) unstable; urgency=medium * check for client->config_listen * send replica lag when reject connection -- Yandex Database Team Tue, 10 Feb 2026 07:46:49 +0000 odyssey (1.5.1-rc1-1) unstable; urgency=medium * fix large packets performance with tls enabled * fix cancel query race * hanging server connections for balancer mode fix * anothe little build and runtime fixes -- Yandex Database Team Thu, 05 Feb 2026 13:37:39 +0000 odyssey (1.5.0-1) unstable; urgency=medium * enable pool_smart_discard when disabling pool_discard * disable pool_discard if use reserve_prepared_statement * set pool_reserve_prepared_statement to yes by default * rate limiting for conn dropping * introduce smart_search_path_enquoting * fix compatibility of musl and support previously unsupported arch, like ppc64le * nginx-like online restart on SIGUSR2, with sd_notify support * support json log format * several fixes, including memory leaks and rare connections stuck from 1.4.1 -- Yandex Database Team Wed, 28 Jan 2026 11:15:05 +0000 odyssey (1.4.1) stable; urgency=low * Odyssey: scalable PostgreSQL connection pooler -- MDB Wed, 22 Jun 2022 08:38:41 +0000 odyssey-1.6.0-rc1/debian/control000066400000000000000000000023421526064202200165250ustar00rootroot00000000000000Source: odyssey Section: database Priority: optional Maintainer: mdb Standards-Version: 4.6.2 Rules-Requires-Root: no Build-Depends: debhelper-compat (= 13), make, cmake, libssl-dev (>= 1.0.1), libpam-dev, libldap-dev, libsystemd-dev, bison, flex Homepage: https://pg-odyssey.tech Vcs-Git: https://github.com/yandex/odyssey.git Vcs-Browser: https://github.com/yandex/odyssey Package: odyssey Architecture: any Recommends: postgresql-client Depends: ${shlibs:Depends}, ${misc:Depends} Description: Scalable PostgreSQL connection pooler Odyssey is an advanced multi-threaded PostgreSQL connection pooler and request router designed for high-performance production environments. . Key features: * Multi-threaded architecture for better CPU utilization * Advanced connection pooling with transaction and session modes * Client SSL/TLS support * PostgreSQL SSL/TLS support * LDAP and PAM authentication * Configurable logging and monitoring Package: odyssey-dbg Architecture: any Section: debug Depends: ${misc:Depends}, odyssey (= ${binary:Version}) Description: Debug symbols for odyssey odyssey-1.6.0-rc1/debian/copyright000066400000000000000000000032041526064202200170530ustar00rootroot00000000000000Upstream-Name: Odyssey Source: https://github.com/yandex/odysseys Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Files: * Copyright: Copyright (C) 2018 YANDEX LLC License: yandex-bsd Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. odyssey-1.6.0-rc1/debian/rules000077500000000000000000000004621526064202200162030ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # NOTE: Only TABs allowed here !!! BUILD_TYPE ?= build_release override_dh_strip: dh_strip --dbg-package=odyssey-dbg override_dh_usrlocal: override_dh_auto_build: make $(BUILD_TYPE) override_dh_auto_install: dh_auto_install --destdir=debian/odyssey %: dh $@ odyssey-1.6.0-rc1/debian/source/000077500000000000000000000000001526064202200164215ustar00rootroot00000000000000odyssey-1.6.0-rc1/debian/source/format000066400000000000000000000000151526064202200176300ustar00rootroot000000000000003.0 (native) odyssey-1.6.0-rc1/docker-compose.yml000066400000000000000000000012401526064202200173310ustar00rootroot00000000000000version: '3' services: odyssey: build: dockerfile: ./docker/Dockerfile context: . environment: CMAKE_BUILD_TYPE: "${CMAKE_BUILD_TYPE:-Release}" ports: - "6432:6432" volumes: - ./odyssey.conf:/etc/odyssey.conf - ..:/odyssey dev: build: dockerfile: ./docker/dev/Dockerfile context: . security_opt: # options needed for gdb debugging - seccomp:unconfined - apparmor:unconfined container_name: odyssey_dev ports: - "7776:22" - "7777:7777" volumes: - .:/code openldapr: image: "osixia/openldap:1.5.0" ports: - "389:389" - "636:636" odyssey-1.6.0-rc1/docker/000077500000000000000000000000001526064202200151465ustar00rootroot00000000000000odyssey-1.6.0-rc1/docker/dev-envs/000077500000000000000000000000001526064202200166755ustar00rootroot00000000000000odyssey-1.6.0-rc1/docker/dev-envs/Dockerfile.ppc64el000066400000000000000000000023341526064202200221450ustar00rootroot00000000000000# sudo apt install qemu-system-ppc qemu-user-static # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker build --platform linux/ppc64le -t dev-env-ubuntu-ppc64el-ssh -f docker/dev-envs/Dockerfile.ppc64el . # docker run -d --name ppc64el-dev --platform linux/ppc64le -p 2222:22 dev-env-ubuntu-ppc64el-ssh FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt-get update && apt-get install -y \ gcc g++ \ gdb \ make cmake \ git \ vim \ strace \ sudo \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ bison flex \ libsystemd-dev RUN apt-get install -y --no-install-recommends python3 openssh-server RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config RUN useradd -m -s /bin/bash -G sudo developer RUN echo 'developer:developer' | chpasswd EXPOSE 22 WORKDIR /workspace COPY . /workspace CMD ["/usr/sbin/sshd", "-D"]odyssey-1.6.0-rc1/docker/dpkg/000077500000000000000000000000001526064202200160735ustar00rootroot00000000000000odyssey-1.6.0-rc1/docker/dpkg/Dockerfile.bionic000066400000000000000000000024121526064202200213260ustar00rootroot00000000000000FROM ubuntu:bionic AS builder-env RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt-get update -y && apt-get install -y --no-install-recommends \ git \ libssl-dev \ wget \ libldap-common \ openssl \ libpam0g-dev \ libldap2-dev libldap-2.4-2 \ build-essential \ make \ tzdata \ software-properties-common \ libsystemd-dev \ bison flex \ debhelper debootstrap devscripts equivs RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null RUN apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" RUN apt-get update && apt-get install -y --no-install-recommends cmake RUN apt-get install -y -t bionic-backports debhelper RUN apt-get clean autoclean; \ apt-get autoremove --yes; \ rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN dpkg-reconfigure --frontend noninteractive tzdata FROM builder-env COPY . /odyssey WORKDIR /odyssey RUN BUILD_TYPE=build_relwithdbginfo dpkg-buildpackage -us -uc RUN /odyssey/docker/dpkg/copy-package-files.sh odyssey-1.6.0-rc1/docker/dpkg/Dockerfile.jammy000066400000000000000000000024021526064202200211770ustar00rootroot00000000000000FROM ubuntu:jammy AS builder-env RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt-get update -y && apt-get install -y --no-install-recommends \ git \ libssl-dev \ wget \ libldap-common \ openssl \ libpam0g-dev \ libldap2-dev libldap-2.5-0 \ build-essential \ make cmake \ tzdata \ libsystemd-dev \ bison flex \ debhelper debootstrap devscripts equivs RUN apt-get clean autoclean; \ apt-get autoremove --yes; \ rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN dpkg-reconfigure --frontend noninteractive tzdata # RUN git clone --depth 1 --branch v1.72.0 https://github.com/aws/aws-lc.git # RUN cd aws-lc && cmake -B build -DBUILD_TESTING=OFF -DDISABLE_GO=ON -DCMAKE_BUILD_TYPE=release && make -C build -j$(nproc) FROM builder-env COPY . /odyssey WORKDIR /odyssey # RUN OPENSSL_LIBRARIES='/aws-lc/build/ssl/libssl.a;/aws-lc/build/crypto/libcrypto.a' OPENSSL_INCLUDE_DIR=/aws-lc/include BUILD_TYPE=build_relwithdbginfo dpkg-buildpackage -us -uc RUN BUILD_TYPE=build_relwithdbginfo dpkg-buildpackage -us -uc RUN /odyssey/docker/dpkg/copy-package-files.sh odyssey-1.6.0-rc1/docker/dpkg/copy-package-files.sh000077500000000000000000000002121526064202200220700ustar00rootroot00000000000000#!/bin/bash set -eux mkdir -p /packages for result in $(find / -maxdepth 1 -name "odyssey*" -type f); do cp $result /packages done odyssey-1.6.0-rc1/docker/dpkg/runner.sh000077500000000000000000000016501526064202200177450ustar00rootroot00000000000000#!/usr/bin/env bash set -ex codename="bionic" output_folder="packages" while getopts ":hc:o:" option; do case $option in c) codename=$OPTARG;; o) output_folder=$OPTARG;; h) cat << EOM $(basename "$0") [-h] [-c codename] [-o output folder] -- build odyssey deb in docker and save packages on host where: -h show this help text -c codename of ubuntu distro (bionic, jammy, etc) -o output folder EOM exit 1;; \?) echo "Error: Invalid option" exit 1;; esac done mkdir -p "$output_folder" image_name="odyssey/dpkg-$codename" container_name="odyssey-packages-$codename" docker build . --tag $image_name -f "./docker/dpkg/Dockerfile.$codename" docker rm -f $container_name || true docker create --name "$container_name" "$image_name" docker cp $container_name:/packages $output_folder docker rm -f $container_name odyssey-1.6.0-rc1/docker/format/000077500000000000000000000000001526064202200164365ustar00rootroot00000000000000odyssey-1.6.0-rc1/docker/format/Dockerfile000066400000000000000000000012441526064202200204310ustar00rootroot00000000000000FROM ubuntu:noble RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true RUN apt-get install -y --allow-unauthenticated --no-install-recommends clang-format-18 python3 python-is-python3 git RUN git clone https://github.com/Sarcasm/run-clang-format.git COPY docker/format/run-format.sh /run.sh RUN chmod +x /run.sh WORKDIR /odyssey ENTRYPOINT [ "/run.sh" ] odyssey-1.6.0-rc1/docker/format/run-format.sh000066400000000000000000000001421526064202200210610ustar00rootroot00000000000000#!/bin/bash /run-clang-format/run-clang-format.py -r --clang-format-executable clang-format-18 $@odyssey-1.6.0-rc1/docker/quickstart/000077500000000000000000000000001526064202200173405ustar00rootroot00000000000000odyssey-1.6.0-rc1/docker/quickstart/Dockerfile000066400000000000000000000023531526064202200213350ustar00rootroot00000000000000FROM debian:trixie-slim AS build RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ cmake \ make \ git \ libssl-dev \ libpam0g-dev \ libldap-dev \ bison flex \ && rm -rf /var/lib/apt/lists/* RUN mkdir /odyssey WORKDIR /odyssey COPY . ./ RUN make clean && make build_release FROM debian:trixie-slim RUN apt-get update && apt-get install -y --no-install-recommends \ libssl3 \ libldap-dev \ libpam0g \ tini \ gosu \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=build /odyssey/build/sources/odyssey . COPY ./docker/quickstart/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh RUN mkdir /etc/odyssey RUN groupadd -g 1001 odyssey && \ useradd -u 1001 -g odyssey -s /sbin/nologin -M odyssey && \ chown -R odyssey:odyssey /app && \ chown odyssey:odyssey /usr/local/bin/entrypoint.sh && \ chown -R odyssey:odyssey /etc/odyssey LABEL org.opencontainers.image.source="https://github.com/yandex/odyssey" LABEL org.opencontainers.image.description="Odyssey Scalable PostgreSQL connection pooler" LABEL org.opencontainers.image.licenses="BSD-3-Clause" ENTRYPOINT ["/usr/bin/tini", "--", "entrypoint.sh"] odyssey-1.6.0-rc1/docker/quickstart/config.conf000066400000000000000000000011461526064202200214560ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_session yes log_query no log_config yes coroutine_stack_size 24 daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "session" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/docker/quickstart/entrypoint.sh000066400000000000000000000126441526064202200221160ustar00rootroot00000000000000#!/bin/sh set -e CONFIG_FILE="/etc/odyssey/odyssey.conf" if [ ! -f "$CONFIG_FILE" ]; then DB_VALUE=${DB_NAME:+\"${DB_NAME}\"} DB_VALUE=${DB_VALUE:-default} USER_VALUE=${USER_NAME:+\"${USER_NAME}\"} USER_VALUE=${USER_VALUE:-default} VIRTUAL_DB_VALUE=${VIRTUAL_DB_NAME:+\"${VIRTUAL_DB_NAME}\"} VIRTUAL_DB_VALUE=${VIRTUAL_DB_VALUE:-\"console\"} VIRTUAL_USER_VALUE=${VIRTUAL_DB_USER_NAME:+\"${VIRTUAL_DB_USER_NAME}\"} VIRTUAL_USER_VALUE=${VIRTUAL_USER_VALUE:-\"console\"} cat > "$CONFIG_FILE" < | default { users }` Defines database name requested by client. Each `database` section structure consists of `user` subsections. A special `database default` is used when no database is matched. `user | default { options }` Defines authentication, pooling and storage settings for requested route. A special `user default` is used when no user is matched. --- ## Configuration Parameters Reference | Parameter | Type | Default | Reload | Notes | |-----------------------------------|----------------------------------------|---------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | authentication | string (none/block/clear_text/external/md5/scram-sha-256/cert) | — (not set) | runtime (new connections) | Must be set per rule; validated to one of: none, block, clear_text, md5, scram-sha-256, cert. If omitted, config validation errors ("authentication mode is not defined"). | | password | string | — (not set) | runtime (new connections) | Route auth secret (plain, MD5 hash, or SCRAM secret). | | auth_common_name | string/list + keyword default | — (none) | runtime (new connections) | For cert auth. You can list CNs; special keyword default toggles "accept any CN" for certificates. | | auth_query | string | — (not set) | runtime (new connections) | External auth SQL query; optional parameter for custom authentication logic. | | auth_pam_service | string | — (not set) | runtime (new connections) | PAM service name (only available if PAM support is compiled in). | | client_max | integer | 0 | runtime (new connections) | Per-route client connection limit; 0 = unlimited connections. | | storage | string | — (not set) | runtime (new connections) | Storage block reference containing backend connection details (endpoints, TLS, etc.). Must be configured for the route to be usable. | | storage_db | string | — (not set) | runtime (new connections) | Overrides database name for backend connections. | | storage_user | string | — (not set) | runtime (new connections) | Overrides username for backend connections. | | storage_password | string | — (not set) | runtime (new connections) | Password for backend connections. | | ldap_endpoint_name | string | — (not set) | runtime (new connections) | LDAP endpoint name used when LDAP authentication is enabled. | | ldap_storage_credentials_attr | string | — (not set) | runtime (new connections) | LDAP attribute name to extract storage credentials from LDAP response. | | ldap_pool_size | integer | 0 | runtime (new connections) | LDAP connection pool size; 0 = no pooling. | | ldap_pool_timeout | integer (ms) | 0 | runtime (new connections) | Timeout for getting connection from LDAP pool. | | ldap_pool_ttl | integer (sec) | 0 | runtime (new connections) | Time-to-live for idle connections in LDAP pool. | | password_passthrough | boolean (yes/no) | no (0) | runtime (new connections) | Enables password passthrough mode; allows forwarding client credentials to backend. | | pool | string (session/transaction/statement) | — (not set) | runtime (new connections) | Required: connection pooling mode. Must be explicitly configured. | | pool_size | integer | 0 | runtime (new connections) | Maximum connections in pool; 0 = unlimited. | | min_pool_size | integer | 0 | runtime (new connections) | Minimum connections to maintain in pool. | | pool_timeout | integer (ms) | 0 | runtime (new connections) | Maximum wait time for acquiring connection from pool. | | pool_ttl | integer (sec) | 0 | runtime (new connections) | Time-to-live for idle server connections. | | pool_discard | boolean | yes (1) | runtime (new connections) | Execute DISCARD ALL when returning connections to pool. | | pool_smart_discard | boolean | no (0) | runtime (new connections) | Use custom discard query instead of DISCARD ALL when enabled. | | pool_discard_query | string | — (not set) | runtime (new connections) | Custom discard query; if includes DEALLOCATE ALL, prepared statements are disallowed. | | pool_cancel | boolean | yes (1) | runtime (new connections) | Send cancel request to backend when client disconnects. | | pool_rollback | boolean | yes (1) | runtime (new connections) | Execute ROLLBACK when returning connections with open transactions. | | client_fwd_error | boolean | no (0) | runtime (new connections) | Forward backend connection errors to client during connection establishment. | | application_name_add_host | boolean | no (0) | runtime (new connections) | Append client hostname to application_name parameter. | | reserve_session_server_connection | boolean | yes (1) | runtime (new connections) | Immediately establish backend connection when client connects. | | server_lifetime | integer (sec) | 3600 | runtime (new connections) | Maximum lifetime for backend connections (1 hour default). | | server_drop_on_cached_plan_error | boolean(yes/no) | no | runtime (new connections) | Do drop server connection on ERROR 0A000 cached plan must not change result type | | server_drop_on_oom | boolean (yes/no) | yes (1) | runtime (new connections) | Drop the client and server connections when the server process reports OOM. | | pool_client_idle_timeout | integer (sec) | 0 | runtime (new connections) | Timeout for idle client connections; only applies to session pooling mode. | | pool_idle_in_transaction_timeout | integer (sec) | 0 | runtime (new connections) | Timeout for idle clients with open transactions; session pooling only. | | pool_reserve_prepared_statement | boolean | yes (0) | runtime (new connections) | Enable prepared statement support; incompatible with session pooling and certain discard modes. | | server_pstmt_cache_size | integer | 0 | runtime (new connections) | Per-server limit of reserved prepared statements; evicts statements not recently used (SIEVE policy) on server reset; 0 = unlimited. Only meaningful with prepared statement support enabled. | | pool_pin_on_listen | boolean | no (0) | runtime (new connections) | Enable pinning client to server after LISTEN execution | | log_debug | boolean | no (0) | runtime (new connections) | Enable debug logging for this route. | | group_checker_interval | integer (ms) | 7000 (global) | runtime (global) | Global setting: interval for checking group membership changes (7 seconds default). | | maintain_params | boolean | yes (1) | runtime (new connections) | Maintain client connection parameters across backend connections for compatibility. | | target_session_attrs | string enum | — (not set) | runtime (new connections) | Target session attributes for connection routing; defaults to undefined behavior. | | quantiles | string (comma-separated) | — (not set) | runtime (new connections) | Comma-separated list of quantile values for statistics collection; disabled when not set. | | catchup_timeout | integer (sec) | 0 | runtime (new connections) | Timeout for replica catchup operations; 0 = no timeout. | | catchup_checks | integer | 0 | runtime (new connections) | **Deprecated.** Accepted but ignored. | | role | string (admin/client) | — (not set) | runtime (new connections) | Route role. `"admin"` grants access to the admin console. | | client_show_id | boolean | no (0) | runtime (new connections) | Include Odyssey client ID in log messages for this route. | | pool_routing | string | — (not set) | runtime (new connections) | Internal routing mode. Use `"internal"` for watchdog/system routes. | | pool_reset_timeout_ms | integer (ms) | 0 | runtime (new connections) | Timeout for executing the reset/discard query when returning a connection to the pool; 0 = no timeout. | | auth_module | string | — (not set) | runtime (new connections) | Name of the external authentication module to use. | | enable_mdb_iamproxy_auth | boolean | no (0) | runtime (new connections) | Enable Yandex MDB IAM proxy authentication. | | mdb_iamproxy_socket_path | string | — (not set) | runtime (new connections) | Path to the MDB IAM proxy UNIX socket. | | log_query (per-route) | boolean | no (0) | runtime (new connections) | Log queries for this specific route (overrides global `log_query`). | | ldap_pool_size | integer | 0 | runtime (new connections) | **Deprecated.** Accepted but ignored. | | ldap_pool_timeout | integer (ms) | 0 | runtime (new connections) | **Deprecated.** Accepted but ignored. | | ldap_pool_ttl | integer (sec) | 0 | runtime (new connections) | **Deprecated.** Accepted but ignored. | ## **authentication** *string* Set route authentication method. Supported: ``` "none" - authentication turned off "block" - block this user "clear_text" - PostgreSQL clear text authentication "md5" - PostgreSQL md5 authentication "scram-sha-256" - PostgreSQL scram-sha-256 authentication "cert" - Compare client certificate Common Name against auth_common_name's ``` `authentication "none"` --- ## **password** *string* Set route authentication password. Depending on selected method, password can be in plain text, md5 hash or SCRAM secret. To generate SCRAM secret you can use [this](https://github.com/DenisMedeirosBBD/PostgresSCRAM256PasswordGenerator) tool. `password "test"` --- ## **auth\_common\_name** default|*string* Specify common names to check for "cert" authentication method. If more than one common name is defined, all of them will be checked until a match is found. Set 'default' to check for the current user. ``` auth_common_name default auth_common_name "test" ``` --- ## **auth\_query** *string* Enable remote route authentication. Use another route to authenticate clients following this logic: Use the selected 'auth\_query\_db' and 'auth\_query\_user' to match a route. Use the matched route server to send 'auth\_query' to get the username and password needed to authenticate a client. ``` auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_db "" auth_query_user "" ``` Disabled by default. --- ## **auth\_pam\_service** Enables PAM (Pluggable Authentication Modules) as the authentication mechanism. It is incompatible with the auth query method. The password must be passed in plain text form, as standard PostgreSQL requires. ``` auth_pam_service "name desired pam service" ``` --- ## **client\_max** *integer* Set client connections limit for this route. Comment out 'client\_max' to disable the limit. When the client limit is reached, Odyssey will reply with 'too many connections'. `client_max 100` --- ## **storage** *string* Set remote server to use. By default, route database and user names are used as connection parameters to the remote server. It is possible to override these values by specifying `storage_db` and `storage_user`. The remote server password can be set using the `storage_password` field. ```plain storage "postgres_server" storage_db "database" storage_user "test" storage_password "test" ``` --- ## **ldap_storage_credentials** This subsection must be located within the `user` subsection and is used to route clients to a remote PostgreSQL server with special credentials (`storage_user` and `storage_password`), depending on the client account attributes stored on the LDAP server (based on OpenLDAP, Active Directory or others). This routing method allows to grant access with different privileges to different databases located on the same host. This routing method may only be used if the variables ldap_endpoint_name and ldap_storage_credentials_attr are set. For example: ``` storage "test_server" { type "remote" port 5432 host "postgres_server" } ldap_endpoint "ldap1" { ldapscheme "ldap" ldapbasedn "dc=example,dc=org" ldapbinddn "cn=admin,dc=example,dc=org" ldapbindpasswd "admin" ldapsearchfilter "(memberOf=cn=localhost,ou=groups,dc=example,dc=org)" ldapsearchattribute "gecos" ldapserver "192.168.233.16" ldapport 389 } database default { user default { authentication "clear_text" storage "test_server" ldap_endpoint_name "ldap1" ldap_storage_credentials_attr "memberof" ldap_storage_credentials "group_ro" { ldap_storage_username "ldap_ro" ldap_storage_password "password1" } ldap_storage_credentials "group_rw" { ldap_storage_username "ldap_rw" ldap_storage_password "password2" } #other required regular parameters are hidden from this example } } ``` To successfully route the client to the PostgreSQL server with correct credentials, client account attributes stored on the LDAP server must contain three required values separated by the `_` character: hostname of PostgreSQL server (`host` value from `storage` section), name of target `database`, and name of `ldap_storage_credentials` in format `%host_%database_%ldap_storage_credentials` For example, look at `memberof` attributes in [usr4.ldiff](https://github.com/yandex/odyssey/tree/master/docker/ldap): ``` dn: uid=user4,dc=example,dc=org objectClass: top objectClass: account objectClass: posixAccount objectClass: shadowAccount cn: user4 uid: user4 memberof: cn=localhost,ou=groups,dc=example,dc=org memberof: cn=localhost_ldap_db1_group_ro,ou=groups,dc=example,dc=org memberof: cn=localhost_ldap_db2_group_rw,ou=groups,dc=example,dc=org uidNumber: 16860 gidNumber: 101 homeDirectory: /home/user4 loginShell: /bin/bash gecos: user4 userPassword: default shadowLastChange: 0 shadowMax: 0 shadowWarning: 0 ``` --- ## **ldap_storage_credentials_attr** *string* Sets the account attribute name from the LDAP server, whose values will be used to determine the route and parameters for connecting the client to the PostgreSQL server. --- ## **ldap_endpoint_name** *string* Specifies the name of the ldap_endpoint to be used to connect to the LDAP server. --- ## **ldap_endpoint** The ldap_endpoint section is used to configure parameters for connecting to the LDAP server. For example: ``` ldap_endpoint "ldap1" { ldapscheme "ldap" ldapbasedn "dc=example,dc=org" ldapbinddn "cn=admin,dc=example,dc=org" ldapbindpasswd "admin" ldapsearchattribute "gecos" ldapserver "192.168.233.16" ldapport 389 } ``` The way the client DN is determined depends on whether `ldapbasedn` is set: * **search+bind** — used when `ldapbasedn` is set. Odyssey searches the directory below `ldapbasedn` for the entry matching the connecting user and then binds with the DN it found. * **simple bind** — used when `ldapbasedn` is not set. No search is performed: the client DN is formed by concatenating `ldapprefix`, the user name the client connected with, and `ldapsuffix`. The scope of the search in search+bind mode is not configurable: Odyssey always searches the whole subtree below `ldapbasedn`. The `ldapscope` option is accepted by the configuration parser and shown when the configuration is dumped, but its value is not used. PostgreSQL behaves the same way and has no separate option for the search scope either. In both modes Odyssey first opens the connection to the LDAP server and binds with `ldapbinddn` / `ldapbindpasswd`, or anonymously when they are not set. `ldapprefix` and `ldapsuffix` are ignored when `ldapbasedn` is set. The names and the meaning of these two options follow the options of the same name in PostgreSQL `pg_hba.conf`. ``` ldap_endpoint "ldap2" { ldapscheme "ldap" ldapserver "192.168.233.16" ldapport 389 ldapprefix "cn=" ldapsuffix ",ou=people,dc=example,dc=org" } ``` With this endpoint a client connecting as `user4` is authenticated as `cn=user4,ou=people,dc=example,dc=org`. --- ## **ldap\_pool\_size** *integer* LDAP server pool size Keep the number of servers in the pool up to 'pool\_size'. Clients are put in a wait queue when all servers are busy. Set to zero to disable. `ldap_pool_size 10` --- ## **ldap\_pool\_timeout** *integer* LDAP server pool timeout Time to wait in milliseconds for an available server. Disconnect the client when the timeout is reached. Set to zero to disable. `ldap_pool_timeout 1000` --- ## **ldap\_pool\_ttl** *integer* LDAP server pool idle timeout. Close the LDAP server connection when it becomes idle for 'ldap\_pool\_ttl' seconds. Set to zero to disable. `ldap_pool_ttl 60` --- ## **password_passthrough** *bool* By default, Odyssey authenticates users itself, but if a side authentication application is used, such as a LDAP server, PAM module, or custom auth module, sometimes instead of configuring `storage_password`, it is more convenient to reuse the client-provided password to perform backend authentication. If you set this option to "yes", Odyssey will store the client token and use it when a new server connection is opened. However, if you configure `storage_password` for the route, `password_passthrough` is essentially ignored. --- ## **pool** *string* Set route server pool mode. Supported modes: ``` "session" - assign server connection to a client until it disconnects "transaction" - assign server connection to a client for a transaction processing ``` `pool "transaction"` --- ## **pool\_size** *integer* Server pool size. Keep the number of servers in the pool up to 'pool\_size'. Clients are put in a wait queue when all servers are busy. Set to zero to disable the limit. `pool_size 100` --- ## **min_pool_size** *integer* Minimum server pool size. Keep the number of servers in the pool at a minimum of 'min_pool_size'. Helps to handle unexpected high load. Default: 0 `min_pool_size 50` --- ## **pool\_timeout** *integer* Server pool wait timeout. Time to wait in milliseconds for an available server. Disconnect the client when the timeout is reached. Set to zero to disable. `pool_timeout 4000` --- ## **pool\_ttl** *integer* Server pool idle timeout. Close a server connection when it becomes idle for 'pool\_ttl' seconds. Set to zero to disable. `pool_ttl 60` --- ## **pool\_discard** *yes|no* Server pool parameters discard. Execute `DISCARD ALL` and reset client parameters before using a server from the pool. `pool_discard no` --- ## **pool\_smart\_discard** *yes|no* When this parameter is enabled, Odyssey sends a smart discard query instead of the default `DISCARD ALL` when it returns a connection to the pool. Its default value may be overwritten by the pool\_discard\_query setting. --- ## **pool\_discard\_query** *string* When resetting a database connection, a pre-defined query string is sent to the server. This query string consists of a set of SQL statements that will be executed during a `DISCARD ALL` command, except for `DEALLOCATE ALL`. The default query string includes the following statements: ```sql SET SESSION AUTHORIZATION DEFAULT; RESET ALL; CLOSE ALL; UNLISTEN *; SELECT pg_advisory_unlock_all(); DISCARD PLANS; DISCARD SEQUENCES;DISCARD TEMP; ``` This sequence of statements is designed to reset the connection to a clean state, without affecting the authentication credentials of the session. By executing these queries, any open transactions will be closed, locks will be released, and any cached execution plans and sequences will be discarded. --- ## **pool\_cancel** *yes|no* Server pool auto-cancel. Start an additional Cancel connection if the server is left with an executing query. Close the connection otherwise. `pool_cancel no` --- ## **pool\_rollback** *yes|no* Server pool auto-rollback. Execute 'ROLLBACK' if the server is left in an active transaction. Close the connection otherwise. `pool_rollback yes` --- ## **client\_fwd\_error** *yes|no* Forward PostgreSQL errors during remote server connection. `client_fwd_error no` --- ## **application_name_add_host** *yes|no* Add the client host name to the application_name parameter `application_name_add_host yes` --- ## **reserve_session_server_connection** *yes|no* Connect a new client to the server immediately or wait for the first query `reserve_session_server_connection yes` --- ## **server_lifetime** *integer* Server lifetime - maximum number of seconds for a server connection to live. Prevents cache bloat. Server connection is deallocated only in the idle state. Defaults to 3600 (1 hour). Use 0 to disable. `server_lifetime 3600` --- ## *server_drop_on_cached_plan_error* *yes/no* Drop server connection when prepared statements support are enabled and `ERROR 0A000 cached plan must not change result type` are met. Useful when driver cant do Close+Prepare by itself. Default: no `server_drop_on_cached_plan_error yes` --- ## *server_drop_on_oom* *yes/no* Drop both the client connection and its attached server connection when the server process has entered an out-of-memory state. This prevents reuse of a server connection that encountered OOM. Default: yes Set `server_drop_on_oom no` to keep the previous behavior. `server_drop_on_oom yes` --- ## **pool\_client\_idle\_timeout** *integer* Client pool idle timeout. Drop stale client connections after this many seconds of idleness when not in a transaction. Set to zero to disable. `pool_client_idle_timeout 0` --- ## **pool\_idle\_in\_transaction\_timeout** *integer* Client pool idle in transaction timeout. Drop client connections in a transaction after this many seconds of idleness. Set to zero to disable. `pool_idle_in_transaction_timeout 0` --- ## **pool\_notice\_after\_waiting\_ms** *integer* Sent NOTICE to client after specified amount of milliseconds spent int waiting for free backend connection. Negative values disables the feature. ``` db1=> select 42; NOTICE: waiting for free backend connection NOTICE: waiting took 12319 ms ?column? ---------- 42 (1 row) ``` `pool_notice_after_waiting_ms 5000` --- ## pool\_attach\_check** *yes/no* Check server connection before attaching to client. Default: yes `pool_attach_check no` --- ## pool\_acquire\_fail\_fast *yes/no* Do not wait for free connection on host if pools are busy, immediately try to get connection from next host Default: no `pool_acquire_fail_fast yes` ### odyssey.opportunistic\_acquire *(runtime GUC)* *on/off* Per-client override that can be set at runtime via `SET odyssey.opportunistic_acquire = 'on'`. When enabled, Odyssey never waits for a free connection from the pool — it immediately fails with a "too many connections" error, even if there is only one host. Unlike `pool_acquire_fail_fast`, this option works per-client and does not require multiple hosts; it also does not retry with waiting. Can be inspected with `SHOW odyssey.opportunistic_acquire`. Requires `virtual_processing yes` in the global config. ```sql SET odyssey.opportunistic_acquire = 'on'; SHOW odyssey.opportunistic_acquire; ``` ## **pool_reserve_prepared_statement** *yes|no* Enable support of prepared statements in transactional pooling. `pool_reserve_prepared_statement yes` ## **server_pstmt_cache_size** *integer* Per-server-connection limit of reserved prepared statements. Eviction happens on returning connection to the pool. Eviction is best-effort: statements still referenced by clients or by other servers are not evicted, so the number of reserved statements may stay above the configured limit. Default: 0 (disabled) `server_pstmt_cache_size 512` ## **pool_pin_on_listen** *yes/no* *Experimental* Enable pinning client connection to server, after LISTEN command execute. Default: `no` `pool_pin_on_listen yes` --- ## **log\_debug** *yes|no* Enable verbose mode for a specific route only. `log_debug no` --- ## **group\_checker\_interval** *integer* Soft interval between group checks (in ms) 7000 by default `group_checker_interval 7000` --- ## **maintain_params** *yes|no* User parameters maintenance By default, Odyssey saves parameter values defined by the user and deploys them on server attach if they are different from the server's. This option disables the feature. `maintain_params no` --- ## **target_session_attrs** *string* Target session attributes feature. Odyssey will look up primary/standby for connections of this user, depending on the value set. Possible values are: - read-write - always select host, available for write - read-only - never select host, available for write - prefer-standby - try to select host available for write last of all - any (the default) - select host randomly `target_session_attrs "read-write"` --- ## **quantiles** *string* Compute quantiles of query and transaction times `quantiles "0.99,0.95,0.5"` --- ## **catchup_timeout** *integer* Specify maximum replication lag in seconds for the user See [catchup-timeout.md](../features/catchup-timeout.md) for more details `catchup_timeout 10` --- ## **catchup\_checks** *(deprecated)* *integer* **Deprecated.** Accepted for backwards compatibility but ignored — a deprecation warning is emitted. --- ## **role** *string* Set the route role. Supported values: ``` "admin" - grants access to the Odyssey admin console "client" - regular client route (default when not set) ``` `role "admin"` --- ## **client\_show\_id** *yes|no* Include the internal Odyssey client ID in log messages produced for connections on this route. Useful when correlating logs for a specific database/user pair. `client_show_id yes` --- ## **pool\_routing** *string* Internal routing mode for the connection. Use `"internal"` for system routes such as the `watchdog` sub-section. Normal client routes should leave this unset. `pool_routing "internal"` --- ## **pool\_reset\_timeout\_ms** *integer* Timeout in milliseconds for executing the reset/discard query when a server connection is returned to the pool. If the query does not complete within this time the connection is dropped instead. Set to 0 to disable the timeout. `pool_reset_timeout_ms 3000` --- ## **auth\_module** *string* Name of the external authentication module to use for this route. The module must be registered via `external_auth_socket_path` in the global section. `auth_module "my_auth_module"` --- ## **enable\_mdb\_iamproxy\_auth** *yes|no* Enable Yandex MDB IAM proxy authentication for this route. When set to `yes`, Odyssey will delegate token validation to the MDB IAM proxy service specified by `mdb_iamproxy_socket_path`. `enable_mdb_iamproxy_auth yes` --- ## **mdb\_iamproxy\_socket\_path** *string* Path to the UNIX socket of the MDB IAM proxy service. Used when `enable_mdb_iamproxy_auth yes`. `mdb_iamproxy_socket_path "/var/run/mdb-iamproxy.sock"` --- ## **log\_query** *(per-route)* *yes|no* Enable query logging for this specific route only, without enabling it globally. Overrides the global `log_query` setting for connections matched by this route. `log_query yes` --- ## **options** Key-value block that sets PostgreSQL GUC parameters sent to the server via the startup message (equivalent to `PGOPTIONS`). Each entry is a pair of quoted strings. ```plain options { "search_path" "myschema,public" "TimeZone" "UTC" } ``` --- ## **backend\_startup\_options** Key-value block for additional parameters sent to the backend during the startup handshake. Similar to `options` but intended for parameters that must arrive in the startup packet rather than as `SET` commands. ```plain backend_startup_options { "application_name" "my_app" } ``` --- ## **group** A `group` sub-section inside a `database` block defines a named set of route settings that is selected based on the client's group membership (looked up via a SQL query). A database block can contain multiple `group` entries as well as `user` entries. ``` database "mydb" { group "power_users" { group_query "SELECT group FROM user_groups WHERE usename=$1" group_query_user "monitor" group_query_db "postgres" authentication "md5" storage "postgres_server" pool "transaction" pool_size 20 } user default { authentication "md5" storage "postgres_server" pool "transaction" pool_size 5 } } ``` ### **group\_query** *string* SQL query executed to determine the group of the connecting client. The query must return a single string value (the group name). The client username is passed as `$1`. `group_query "SELECT grp FROM user_groups WHERE usename=$1"` ### **group\_query\_user** *string* Username used to connect to the database when executing `group_query`. `group_query_user "monitor"` ### **group\_query\_db** *string* Database used when executing `group_query`. `group_query_db "postgres"` --- ## Deprecated parameters The following per-route parameters are accepted for backwards compatibility but are **ignored** — a deprecation warning is emitted in the log. | Parameter | Notes | |---|---| | `catchup_checks` | Removed. Use `catchup_timeout` instead. | | `ldap_pool_size` | Removed. | | `ldap_pool_timeout` | Removed. | | `ldap_pool_ttl` | Removed. | --- ## example (remote) ``` database default { user default { authentication "none" # password "" # auth_common_name default # auth_common_name "test" # auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" # auth_query_db "" # auth_query_user "" # client_max 100 storage "postgres_server" # storage_db "database" # storage_user "test" # storage_password "test" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_cancel no pool_rollback yes client_fwd_error no log_debug no } } ``` --- ## example (admin console) ``` storage "local" { type "local" } database "console" { user default { authentication "none" pool "session" storage "local" } } ``` odyssey-1.6.0-rc1/docs/configuration/shared-pools.md000066400000000000000000000003621526064202200224210ustar00rootroot00000000000000# shared_pool Defines shared pool that can be used is rules. See more in [shared pools](../features/shared-pools.md). ## pool_size The size of shared_pool. Must be positive integer. ## Example ``` shared_pool "idm" { pool_size 10 } ``` odyssey-1.6.0-rc1/docs/configuration/soft-oom.md000066400000000000000000000016471526064202200215730ustar00rootroot00000000000000# Soft OOM section See [soft OOM feature](../features/soft-oom.md) for more about soft OOM. ## **limit** *string* Max memory consumption. Supports prefixes B/KB/MB/GB `limit 750MB` ## **process** *string* Process name to monitor memory consumption. Matching by substring in exe path. Max length - 256 `process "postgres"` ## **check_interval_ms** *integer* Pause interval between memory consumption checks in milliseconds. Default: 1000 `check_interval_ms 500` ## **drop** Specify postgres process dropping ### **signal** *string* Name of the signal to send to processes Accept unix names like SIGTERM, SIGKILL Default: SIGTERM `signal "SIGTERM"` ### **max_rate** *integer* Specify max pids to signal every `check_interval_ms` Default: 4 `max_rate 4` ## example ```plaintext soft_oom { limit 1GB process "postgres" check_interval_ms 500 drop { signal SIGTERM max_rate 5 } } ```odyssey-1.6.0-rc1/docs/configuration/storage.md000066400000000000000000000211571526064202200214720ustar00rootroot00000000000000# Storage section Defines server used as a data storage or admin console operations. `storage { options }` --- ## **type** *string* Set storage type to use. Supported types: ``` "remote" - PostgreSQL server "local" - Odyssey (admin console) ``` `type "remote"` Local console supports RELOAD, SHOW and KILL_CLIENT commands. ## **host** *string* Remote server address. If host is not set, Odyssey will try to connect using UNIX socket if `unix_socket_dir` is set. ## **port** *integer* Remote server port. ## **tls** *string* Supported TLS modes: ``` "disable" - disable TLS protocol "allow" - first try a non-SSL connection; if that fails, try an SSL connection "prefer" - first try an SSL connection; if that fails, try a non-SSL connection "require" - TLS required "verify_ca" - require valid certificate "verify_full" - require valid certificate ``` The modes follow the `sslmode` parameter of libpq. `allow` connects in plaintext first. A server that requires TLS refuses such a connection only at startup, with `no pg_hba.conf entry ... no encryption`, and only then does Odyssey drop the connection and retry it over TLS. `backend_connect_timeout_ms` applies to each attempt separately. The retry is not limited to the TLS-related error, since the error itself is not a reliable signal: its text is translated according to the server `lc_messages`, and a server that is not PostgreSQL reports the same situation differently. Like libpq, Odyssey retries whenever the server answered the startup packet with an ErrorResponse, whatever the error is; a connection that was closed or a reply that could not be parsed is reported as is, without a second attempt. Two exceptions are made: * `57P03` (`cannot_connect_now`), just like libpq, which considers another host more promising than another encryption method * `53300` (`too_many_connections`), `3D000` (`invalid_catalog_name`) and `28P01` (`invalid_password`) — unlike libpq. These are definite answers that TLS will not change, and repeating them for every client would only double the connection load on a server that is already refusing connections. Note that with `allow` the startup packet and the authentication exchange of the first attempt are sent in cleartext, and against a server that supports TLS but does not require it the connection stays unencrypted. Use `prefer` to negotiate TLS whenever the server supports it, or `require` to demand it. Cancel requests send no startup packet and therefore have nothing to retry on, so with `allow` they are always sent in plaintext. This does not make them fail: a server handles a cancel request before authentication and without consulting `pg_hba.conf`, so it accepts a plaintext one even when it otherwise allows `hostssl` connections only. The cancel key does travel in cleartext then, just as it does with the signal-safe `PQcancel()` of libpq. Use `prefer` or `require` if that matters. Connections preallocated for `min_pool_size` are established in plaintext as well, since they are connected long before they are used. Such a connection is retried over TLS when a client picks it up and the startup is finally performed. !!! warning Earlier versions of Odyssey negotiated TLS first for `allow` and fell back to plaintext, i.e. `allow` behaved the way `prefer` does now. Replace it with `prefer` to keep that behaviour — otherwise connections that used to be encrypted become plaintext. Odyssey logs a message at startup for every storage that still uses `allow`. ## **tls\_ca\_file** *string* Path to CA certificate file used to verify the server certificate (for `verify_ca` / `verify_full` modes). `tls_ca_file "/etc/odyssey/ssl/allCAs.pem"` ## **tls\_key\_file** *string* Path to client private key file used for mutual TLS authentication. `tls_key_file "/etc/odyssey/ssl/client.key"` ## **tls\_cert\_file** *string* Path to client certificate file used for mutual TLS authentication. `tls_cert_file "/etc/odyssey/ssl/client.crt"` ## **tls\_protocols** *string* Allowed TLS protocol versions. `tls_protocols "tlsv1.2"` ## **endpoints_status_poll_interval** *integer* If target_session_attrs is set (from listen or query), Odyssey will check every endpoint attrs not more often than endpoints_status_poll_interval milliseconds within one conn Default 1000 `endpoints_status_poll_interval 1000` ## **server_max_routing** *integer* Global limit of server connections concurrently being routed. We are opening no more than server_max_routing server connections concurrently. Unset or zero 'server_max_routing' will set it's value equal to number of workers `server_max_routing 4` ## **balancing** Optional sub-section that configures load-balancing behaviour for this storage. See [balancing](../features/balancing.md) for a full overview. ### **method** *string* Balancing method name. Defines how Odyssey picks among the storage endpoints. ```plain balancing { method "roundrobin" { } } ``` #### **az\_aware** *yes|no* Controls whether the balancing method takes the instance's availability zone into account when selecting endpoints. Defaults to `yes`. When `az_aware yes` (the default), Odyssey prefers endpoints located in the same availability zone as the instance (as configured by `availability_zone` in the [global section](../configuration/global.md)). Endpoints in other zones are used only when no same-AZ endpoints are available. Set to `no` to disable AZ-aware selection and treat all endpoints equally regardless of their availability zone. ```plain balancing { method "roundrobin" { az_aware no } } ``` ### **show\_notice\_messages** *yes|no* When set to `yes`, Odyssey sends PostgreSQL NOTICE messages to the client when a balancing decision is made. Defaults to `no`. ```plain balancing { method "roundrobin" {} show_notice_messages yes } ``` ## **watchdog** Storage lag-polling watchdog. Defines storage lag-polling watchdog options and actually enables a cron-like watchdog for this storage. The watchdog periodically executes `watchdog_lag_query` against the storage server, then uses the result to decide whether connecting to that host is desirable given the current replication lag. ### How the query result is interpreted The query **must return a single integer** — the Unix timestamp (seconds since epoch) of the last WAL data replayed on this replica, i.e. the point in time *up to which* the replica has replayed the WAL. Odyssey computes the lag as: ``` lag_sec = current_unix_time - query_result ``` This is **not** a lag value in seconds — it is the replay timestamp itself. Odyssey does the subtraction internally. A convenient way to obtain this value is [repl_mon](https://github.com/man-brain/repl_mon), a PostgreSQL bgworker that writes `current_timestamp` (on the primary) and the current WAL LSN to a small table once per second. On a replica the query reads back that timestamp: ```sql SELECT TRUNC(EXTRACT(EPOCH FROM ts))::int FROM repl_mon LIMIT 1 ``` For a quick test without repl_mon you can use a synthetic expression that returns a fixed timestamp a few seconds in the past: ```sql SELECT TRUNC(EXTRACT(EPOCH FROM NOW()))::int - 100 ``` This simulates a replica that is always 100 seconds behind. ### watchdog\_lag\_query *string* SQL query executed periodically by the watchdog. Must return a single integer: the Unix timestamp (seconds) up to which the replica has replayed WAL. `watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM ts))::int FROM repl_mon LIMIT 1"` ### watchdog\_lag\_interval *integer* Interval in seconds between successive executions of `watchdog_lag_query`. `watchdog_lag_interval 10` ```plain watchdog { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 pool_timeout 0 pool_ttl 1201 log_debug no # Query must return the Unix timestamp up to which the replica has # replayed WAL. Odyssey computes: lag_sec = now() - query_result. # repl_mon (https://github.com/man-brain/repl_mon) writes this value # from the primary once per second; read it back on the replica: watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM ts))::int FROM repl_mon LIMIT 1" watchdog_lag_interval 10 } ``` ## Using storages in `listen` blocks A `listen` block can reference one or more storages by name to enable per-listen storage failover. See the [`storage` option in the listen section](listen.md#storage) for details. ## example ``` storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 # tls "disable" # tls_ca_file "" # tls_key_file "" # tls_cert_file "" # tls_protocols "" } ``` odyssey-1.6.0-rc1/docs/development/000077500000000000000000000000001526064202200171515ustar00rootroot00000000000000odyssey-1.6.0-rc1/docs/development/debugging.md000066400000000000000000000117421526064202200214330ustar00rootroot00000000000000# Odyssey debugging --- All of us make mistakes from time to time, and Odyssey contributors are not exempt from this. The main challenge of Odyssey debugging is that it uses an asynchronous engine to perform efficient I/O operations. Every thread has multiple of coroutines, that are not visible for `gdb`. So we created a [gdb extension](https://github.com/yandex/odyssey/tree/master/sources/machinarium/gdb) to make Odyssey debugging easier. ## How to use Run gdb and then initialize the script: ```bash (gdb) source ~/odyssey/sources/machinarium/gdb/machinarium-gdb.py ``` ## Commands ### info mmcoros List all machinarium coroutines for threads. Shows list of active and ready coroutines from scheduler of the thread. Current coroutine are marked by *. See `(gdb) help info mmcoros` for more. Ex: ```plain (gdb) info mmcoros Thread 3 (benchmark_csw) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x555555557fe0 *1 MM_CACTIVE 0 0x555555557f70 Thread 2 (resolver: 0) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x5555555596e3 Thread 1 (benchmark_csw) machinarium coroutines: The mm_self is NULL, so no coroutines in this thread available. (gdb) info mmcoros 'benchmark_csw' Thread 3 (benchmark_csw) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x555555557fe0 *1 MM_CACTIVE 0 0x555555557f70 Thread 1 (benchmark_csw) machinarium coroutines: The mm_self is NULL, so no coroutines in this thread available. (gdb) info mmcoros 2 3 Thread 3 (benchmark_csw) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x555555557fe0 *1 MM_CACTIVE 0 0x555555557f70 Thread 2 (resolver: 0) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x5555555596e3 ``` ### mmcoro Execute gdb command in context of the specified coroutine. Please note, that coroutine is determined with pair thread id and coroutine id. Also note, that in mostly cases there will be zero-frame, which is fake (this is just how gdb's unwinders works) See `help mmcoro` for more. Ex: ``` (gdb) mmcoro 2 0 bt #1 0x000055555555dd1f in mm_scheduler_yield (scheduler=0x55555557c828) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:166 #2 0x000055555555e106 in mm_call (call=0x7ffff7fb5e08, type=MM_CALL_EVENT, time_ms=4294967295) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/call.c:60 #3 0x000055555555ef3c in mm_eventmgr_wait (mgr=0x55555557c928, event=0x7ffff7fb5e00, time_ms=4294967295) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/event_mgr.c:101 #4 0x000055555555fc76 in mm_channel_read (channel=0x5555555670f0 , time_ms=4294967295) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/channel.c:117 #5 0x00005555555598cf in mm_taskmgr_main (arg=0x0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/task_mgr.c:20 #6 0x000055555555d6fa in mm_scheduler_main (arg=0x7ffff0000b70) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:17 #7 0x00005555555609ab in mm_context_runner () at /home/rkhapov/odyssey/build/third_party/machinarium/sources/context.c:28 #8 0x0000000000000000 in ?? () (gdb) mmcoro 4 0 frame apply 7 info locals Please be careful with analyzing for the frame with zero index Unless your command is not bt, the zero frame will be printed, and this frame is fake. #0 mm_clock_cmp (a=0x0, b=0x0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/clock.c:13 __PRETTY_FUNCTION__ = "mm_clock_cmp" #1 0x000055555555dd1f in mm_scheduler_yield (scheduler=0x55555557d898) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:166 current = 0x7fffe0000b70 resume = 0x55555557d8a0 __PRETTY_FUNCTION__ = "mm_scheduler_yield" #2 0x000055555555e106 in mm_call (call=0x7ffff7ef4ec0, type=MM_CALL_SLEEP, time_ms=0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/call.c:60 scheduler = 0x55555557d898 clock = 0x55555557da50 coroutine = 0x7fffe0000b70 #3 0x0000555555558a03 in machine_sleep (time_ms=0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/machine.c:201 call = {type = MM_CALL_SLEEP, coroutine = 0x7fffe0000b70, timer = {active = 1, timeout = 181455347, interval = 0, seq = 248, callback = 0x55555555dece , arg = 0x7ffff7ef4ec0, clock = 0x55555557da50}, cancel_function = 0x55555555df3f , arg = 0x7ffff7ef4ec0, data = 0x0, timedout = 0, status = 0} #4 0x00005555555580b7 in do_smt2 (arg=0x0) at benchmark_csw.c:71 c = 103 'g' #5 0x000055555555810e in do_smt (arg=0x0) at benchmark_csw.c:79 No locals. #6 0x000055555555d6fa in mm_scheduler_main (arg=0x7fffe0000b70) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:17 coroutine = 0x7fffe0000b70 scheduler = 0x55555557d898 i = 0x0 ``` ### od-list-print(-select) Print od_list (intrusive) content. See `help od-list-print` for more. odyssey-1.6.0-rc1/docs/development/internals.md000066400000000000000000000202021526064202200214660ustar00rootroot00000000000000# Internals --- ### Odyssey architecture and internals Odyssey heavily depends on two libraries, which were originally created during its development: Machinarium and Kiwi. #### Machinarium Machinarium extensively used for organization of multi-thread processing, cooperative multi-tasking and networking IO. All Odyssey threads are run in context of machinarium `machines` - pthreads with coroutine schedulers placed on top of `epoll(7)` event loop. Odyssey does not directly use or create multi-tasking primitives such as OS threads and mutexes. All synchronization is done using message passing and transparently handled by machinarium. Repository: [third\_party/machinarium](https://github.com/yandex/odyssey/tree/master/sources/machinarium) #### Kiwi Kiwi provides functions for constructing, reading and validating PostgreSQL protocol requests messages. By design, all PostgreSQL specific details should be provided by Kiwi library. Repository: [third\_party/kiwi](https://github.com/yandex/odyssey/tree/master/sources/kiwi) #### Core components ``` main() .----------. | instance | thread '----------' .--------. .-------------. | system | | worker_pool | '--------' '-------------' .--------. .---------. .---------. .---------. | router | | servers | | worker0 | ... | workerN | '--------' '---------' '---------' '---------' .---------. .------. thread thread | console | | cron | '---------' '------' ``` #### Instance Application entry point. Handle initialization. Read configuration file, prepare loggers, parse cli options. Run system and worker\_pool threads. [sources/instance.h](https://github.com/yandex/odyssey/blob/master/sources/instance.h), [sources/instance.c](https://github.com/yandex/odyssey/blob/master/sources/instance.c) #### System Start router, cron and console subsystems. Create listen server one for each resolved address. Each listen server runs inside own coroutine. Server coroutine mostly waits on `machine_accept()`. On incoming connection, new client context is created and notification message is sent to next worker using `workerpool_feed()`. Client IO context is not attached to any `epoll(7)` context yet. Handle signals using `machine_signal_wait()`. On `SIGHUP`: do versional config reload, add new databases and obsolete old ones. On `SIGUSR1`: reopen log file. On `SIGUSR2`: graceful shutdown. On `SIGINT`, `SIGTERM`: call `exit(3)`. Other threads are blocked from receiving signals. [sources/system.h](https://github.com/yandex/odyssey/blob/master/sources/system.h), [sources/system.c](https://github.com/yandex/odyssey/blob/master/sources/system.c) #### Router Handle client registration and routing requests. Do client-to-server attachment and detachment. Ensure connection limits and client pool queueing. Handle implicit `Cancel` client request, since access to server pool is required to match a client key. Router works in request-reply manner: client (from worker thread) sends a request message to router and waits for reply. Could be a potential hot spot (not an issue at the moment). [sources/router.h](https://github.com/yandex/odyssey/blob/master/sources/router.h), [sources/router.c](https://github.com/yandex/odyssey/blob/master/sources/router.c) #### Cron Do periodic service tasks, like idle server connection expiration and database config obsoletion. [sources/cron.h](https://github.com/yandex/odyssey/blob/master/sources/cron.h), [sources/cron.c](https://github.com/yandex/odyssey/blob/master/sources/cron.c) #### Worker and worker pool Worker thread (machinarium machine) waits on incoming connection notification queue. On new connection event, create new frontend coroutine and handle client (frontend) lifecycle. Each worker thread can host thousands of client coroutines. Worker pool is responsible for maintaining a thread pool of workers. Threads are machinarium machines, created using `machine_create()`. [sources/worker.h](https://github.com/yandex/odyssey/blob/master/sources/worker.h), [sources/worker.c](https://github.com/yandex/odyssey/blob/master/sources/worker.c), [sources/worker_pool.h](https://github.com/yandex/odyssey/blob/master/sources/worker_pool.h), [sources/worker_pool.c](https://github.com/yandex/odyssey/blob/master/sources/worker_pool.c) #### Single worker mode To reduce multi-thread communication overhead, Odyssey handles case with a single worker (`workers 1`) differently. Instead of creating separate thread + coroutine for each worker, only one worker coroutine created inside system thread. All message channels `machine_channel_create()` created marked as non-shared. This allows to make faster communications without a need to do expensive system calls for event loop wakeup. #### Client (frontend) lifecycle Whole client logic is driven by a single `od_frontend()` function, which is a coroutine entry point. There are 6 distinguishable stages in client lifecycle. [sources/frontend.h](https://github.com/yandex/odyssey/blob/master/sources/frontend.h), [sources/frontend.c](https://github.com/yandex/odyssey/blob/master/sources/frontend.c) #### 1. Startup Read initial client request. This can be `SSLRequest`, `CancelRequest` or `StartupMessage`. Handle SSL/TLS handshake. #### 2. Process Cancel request In case of `CancelRequest`, call Router to handle it. Disconnect client right away. #### 3. Route client Call router. Use `Database` and `User` to match client configuration route. Router assigns matched route to a client. Each route object has a reference counter. All routes are periodically garbage-collected. #### 4. Authenticate client Write client an authentication request `AuthenticationMD5Password` or `AuthenticationCleartextPassword` and wait for reply to compare passwords. In case of success send `AuthenticationOk`. #### 5. Process client requests Depending on selected route storage type, do `local` (console) or `remote` (remote PostgreSQL server) processing. Following remote processing logic repeats until client sends `Terminate`, client or server disconnects during the process: * Read client request. Handle `Terminate`. * If client has no server attached, call Router to assign server from the server pool. New server connection registered and initiated by the client coroutine (worker thread). Maybe discard previous server settings and configure it using client parameters. * Send client request to the server. * Wait for server reply. * Send reply to client. * In case of `Transactional` pooling: if transaction completes, call Router to detach server from the client. * Repeat. #### 6. Cleanup If server is not Ready (query still in-progress), initiate automatic `Cancel` procedure. If server is Ready and left in active transaction, initiate automatic `Rollback`. Return server back to server pool or disconnect. Free client context. #### Client error codes In the most scenarios PostgreSQL error messages `ErrorResponse` are copied to a client as-is. Yet, there are some cases, when Odyssey has to provide its own error message and SQLCode to client. Function `od_frontend_error()` is used for formatting and sending error message to client. | SQLCode | PostgreSQL Code Name | Stage | | ------- | -------------------- | ----- | | **08P01** | `PROTOCOL_VIOLATION` | Startup, TLS handshake, authentication | | **0A000** | `FEATURE_NOT_SUPPORTED` | TLS handshake | | **28000** | `INVALID_AUTHORIZATION_SPECIFICATION` | Authentication | | **28P01** | `INVALID_PASSWORD` | Authentication | | **58000** | `SYSTEM_ERROR` | Routing, System specific | | **3D000** | `UNDEFINED_DATABASE` | Routing | | **53300** | `TOO_MANY_CONNECTIONS` | Routing | | **08006** | `CONNECTION_FAILURE` | Server-side error during connection or IO | PostgreSQL specific error codes can be found in `src/backend/errocodes.txt`. odyssey-1.6.0-rc1/docs/development/local-runs.md000066400000000000000000000031031526064202200215470ustar00rootroot00000000000000# Running things locally --- ## Prerequisites Since **1.5.3** the config parser is generated from a Flex/Bison grammar at build time. Make sure `flex` and `bison` are installed before running cmake. Ubuntu/Debian: ```sh sudo apt-get install flex bison ``` Fedora/RHEL/CentOS: ```sh sudo dnf install flex bison ``` macOS (Homebrew): ```sh brew install flex bison ``` --- Here's a concise list of useful development commands from the Makefile: ### Build & Run - `make local_build` - Clean and build locally in debug mode - `make build_release` - Build release version - `make build_dbg` - Build debug version - `make build_asan` - Build with ASAN for memory debugging - `make local_run` - Run with dev config - `make console_run` - Run in console mode with verbose logging ### Testing - `make functional-test` - Run functional tests - `make stress-tests` - Run stress tests - `make jemalloc-test` - Run tests with jemalloc - `make ci-unittests` - Run unit tests in CI environment ### Debugging - `make gdb` - Build debug version and run in GDB ### Formatting - `make format` - Format code using clang-format - `make check-format` - Check code formatting ### Packaging - `make cpack-deb` - Build DEB package - `make cpack-rpm` - Build RPM package - `make install` - Install to system ### Documentation - `make build-docs-web` - Build documentation - `make serve-docs` - Serve docs locally ### Development Environment - `make dev_run` - Format, build and run (all-in-one) - `make start-dev-env-dbg` - Start debug docker environment - `make start-dev-env-asan` - Start ASAN docker environment odyssey-1.6.0-rc1/docs/development/packaging.md000066400000000000000000000016251526064202200214230ustar00rootroot00000000000000# Packaging ---- > **Note (since 1.5.3):** `flex` and `bison` are required build dependencies. > They are already listed in `debian/control` (`Build-Depends`). > If you are maintaining the RPM spec (`scripts/centos/odyssey.spec`), add > `BuildRequires: flex` and `BuildRequires: bison` to it. To create deb package for local or test-env use, you can do `cpack-*` targets from Makefile. Packages will be created at `build/packages` directory. ```sh make cpack-deb $ ls build/packages odyssey-dbgsym_1.4rc-2276-d458212f_amd64.ddeb odyssey_1.4rc-2276-d458212f_amd64.deb ``` For more convenient way to build package, consider use `dpkg-buildpackage`. Some example can be run by `package-*` targets: ```sh make package-bionic $ ls build/packages odyssey-dbg_1.3-200_amd64.deb odyssey_1.3-200.dsc odyssey_1.3-200.tar.xz odyssey_1.3-200_amd64.buildinfo odyssey_1.3-200_amd64.changes odyssey_1.3-200_amd64.deb ``` odyssey-1.6.0-rc1/docs/development/testing.md000066400000000000000000000034171526064202200211550ustar00rootroot00000000000000# Odyssey testing We try our best to keep Odyssey stable, and testing helps us achieve this goal. This page describes the different types of Odyssey tests and how to create them. ---- ## Unit tests Tests that are written in C and checks some micro invariants all above the code. The directory is [tests](https://github.com/yandex/odyssey/tree/master/test). Use any currently existing test as an example to create new one. ## Functional tests Tests that checks some of Odyssey functionality on high level, located in [docker/functional/tests](https://github.com/yandex/odyssey/tree/master/docker/functional/tests). Run with `pytest`, to add new follow this steps: 1. Create new folder in [docker/functional/tests](https://github.com/yandex/odyssey/tree/master/docker/functional/tests) 2. Create `runner.sh` script inside new folder, that performs test logic (usually it starts Odyssey + performs some `psql` operations) this script must return 0 if test is passed and non-zero if test is failed. 3. New test will be run automatically with `make functional-tests` 4. You can run specific test with `make functional-tests ODYSSEY_TEST_SELECTOR=my-cool-test` 5. All other build type (like debug/asan/tsan) will be run automatically on CI or with with command like `make functional-tests ODYSSEY_TEST_SELECTOR=my-cool-test ODYSSEY_BUILD_TYPE=asan` 6. You can debug your test inside test environment with `make start-dev-env-dbg` We will ask you to add such kind of test in PR's that implements some new options in Odyssey. ## CI Github actions We have several Github actions that are run on each PR. They are located in [.github/workflows](https://github.com/yandex/odyssey/tree/master/.github/workflows). and uses docker images from [docker/](https://github.com/yandex/odyssey/tree/master/docker/). odyssey-1.6.0-rc1/docs/features/000077500000000000000000000000001526064202200164455ustar00rootroot00000000000000odyssey-1.6.0-rc1/docs/features/balancing.md000066400000000000000000000044671526064202200207200ustar00rootroot00000000000000# Balancing load with Odyssey *since 1.4.1* Load balancing enables Odyssey to efficiently distribute incoming client queries across multiple PostgreSQL servers. This improves resource utilization, enhances throughput, and ensures high availability in large-scale deployments. With load balancing, Odyssey manages the assignment of connections or requests, helping to prevent any single server from becoming a bottleneck. ![Odyssey balancing](../img/balancing.svg "Odyssey balancing") Odyssey transparently routes queries to available PostgreSQL backends based on the selected balancing mode, helping to maximize performance and reliability without requiring application-level logic changes. ---- ## Configuration You will simply need to specify several hosts in your storage section: ```plaintext storage "postgres_server" { type "remote" host "pg-host-1:5432,pg-host-2:5432,pg-host-3:5432" } ``` See [storage configuration guide](../configuration/storage.md) for more about storage section. ## Host selection Host is selected randomly, but with next priorities: 0. **Localhost**. Localhost endpoints will be tried first. 1. **Availability zone**. If `availability_zone` is set in [global section](../configuration/global.md), the endpoints with equals az will be tried second. This behaviour is enabled by default and can be turned off per balancing method with [`az_aware no`](../configuration/storage.md#az_aware). 2. **Target session attributes**. Works if [TSA](tsa.md) is specified in some way. Endpoints with equals target session attributes will be tried third. ## Pool settings The pools in unique per each endpoint in host, so if you set `pool_size` to 10, you will get 10 pools per each host, and if there is 3 hosts, you will get 30 total connections. ## Per-listen override The entire `balancing` section can be overridden per listen endpoint using [`balancing`](../configuration/listen.md#balancing) in the `listen` section. When set, it takes precedence over the storage-level `balancing` configuration, while the round-robin counter and other shared state remain in the storage, ensuring fair distribution across all listeners that reference the same storage. ``` listen { host "*" port 6432 balancing { method "leastconn" { az_aware no } show_notice_messages yes } } ```odyssey-1.6.0-rc1/docs/features/catchup-timeout.md000066400000000000000000000033211526064202200221010ustar00rootroot00000000000000# Catchup timeout The catchup timeout feature allows the pooler to automatically drop connections to PostgreSQL hosts whose replication lag exceeds a specified threshold. This ensures that clients do not interact with significantly out-of-date replicas, helping maintain data freshness and consistency in read-scaling or high-availability setups. Catchup timeout is especially valuable when application changes are difficult,providing a simple way to protect against stale reads by enforcing replication health at the pooler level. ---- ## Configuration First of all, you will need to create watchdog in storage section. It looks like this in your configuration file: ```plaintext storage "postgres_server" { type "remote" host "localhost" port 5432 watchdog { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT EXTRACT(EPOCH FROM ts) FROM repl_mon" watchdog_lag_interval 10 } } ``` The most interesting part is `watchdog_lag_query` parameter. Watchdog will execute this query to get underlying server lag. Consider something like `now() - pg_last_xact_replay_timestamp()` or [repl_mon](https://github.com/man-brain/repl_mon) for production usages Also you need specify `catchup_timeout` parameter in user section, that sets maximum replication lag in seconds: ```plaintext database "db" { user "user" { ... catchup_timeout 3 catchup_checks 1 } } ``` See [storage configuration guide](../configuration/storage.md) for more about storage section. odyssey-1.6.0-rc1/docs/features/console.md000066400000000000000000000131111526064202200204260ustar00rootroot00000000000000# Odyssey console Odyssey supports administration console, that are configuraed like 'local' [storage](../configuration/storage.md). This console allows to connect to Odyssey with `psql` like usual and performs some 'admin' operations or retrieve some metrics/statistics. ---- ## Configuration You will need to create a local storage and rule for accessing the console. Odyssey configuration to create an ability for console connections can look like: ```conf storage "local" { type "local" } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } } ``` After that you can connect and execute commands in console with any postgresql client: ```sh psql -h localhost -p 6432 -U console -d console -c "show clients" psql -h localhost -p 6432 -U console -d console -c "show servers" psql -h localhost -p 6432 -U console -d console -c "pause" ``` ## kill_client Drop connection with specified client. `kill_client c123dfsdfg2` ## reload Reload Odyssey configuration. Can be used to set some Odyssey parameters without restarting Odyssey. `reload` ## help Writes available commands to execute in console. `help` ## show ... ### show clients Writes list of currently connected clients. `show clients` ### show servers Writes list of currently connected servers. `show servers` ### show server_prep_stmts Writes list of currently allocated prepared statements. `show server_prep_stmts` ### show pools Write information about currently allocated pools for every database.user `show pools` ### show pools_extended Write even more information about currently allocated pools for every database.user `show pools_extended` Returns every `show pools` column, then `bytes_received`, `bytes_sent`, `tcp_conn_count`, `cl_queue`, and a `query_`/`transaction_` pair per configured quantile. The client counters distinguish three different client states, which is easy to get wrong: | Column | Meaning | | --- | --- | | `cl_active` | Clients currently attached to a backend connection | | `cl_waiting` | Clients holding a route but not attached to a backend, i.e. idle between transactions. This is **not** a sign of pool pressure | | `cl_queue` | Clients blocked waiting for a backend connection to become free. A non-zero value means the pool is exhausted | | `maxwait` | Whole seconds the longest-queued client has been waiting so far, `0` when nothing is queued | | `maxwait_us` | Sub-second remainder of `maxwait`, in microseconds | A client that starts to queue moves out of `cl_waiting` into `cl_queue`, so `cl_waiting` can drop exactly when the pool becomes saturated. Use `cl_queue` and `maxwait` to detect pool exhaustion. ### show storages Write information about current storages that are used to connect to PostgreSQL `show storages` ### show version Write Odyssey version `show version` ### show listen Show list of currently listened addresses `show listen` ### show is_paused Show if Odyssey is paused or not `show is_paused` ### show errors Show statistics about currently appeared errors `show errors` ### show lists Show counts of the objects Odyssey is tracking: pools, clients in each state, servers and DNS cache entries `show lists` ### show instance Show the state of the running Odyssey process itself, as opposed to the objects it is currently routing `show instance` | Field | Description | | --- | --- | | `config_load_failed` | `1` if the last config load failed, so the process kept its previous configuration and a restart would not come up on the file currently on disk. `0` otherwise | ### show databases Show info about databases `show databases` ### show host utilization Show info about host resources utilization, in percentages ```plain console=> show host_utilization; cpu | mem -------+------- 13.37 | 15.77 ``` ### show config Reports the global configuration of the running process. `show config` ```plain console=> show config; key | value | default | changeable -----------------------------+-------+---------+------------ workers | 4 | 1 | no log_query | yes | no | yes ... ``` | Column | Meaning | | --- | --- | | `key` | parameter name, as accepted by the configuration parser | | `value` | value the running process is using right now | | `default` | built-in value used when the parameter is absent from the configuration file | | `changeable` | whether `RELOAD` applies a new value without a restart | Reading `odyssey.conf` tells you what was asked for, not what is in effect. `RELOAD` applies only part of the global configuration; the rest keeps the value the process started with, and no error is reported. The `changeable` column tells the two apart, so comparing `value` against the file is enough to spot a parameter that needs a restart. Notes: - `default` is the built-in initial value. A few parameters are derived from others when left unset - `client_max_routing`, for instance, becomes `workers * 64` - so `value` can differ from `default` even though the configuration file does not mention the parameter. - Values longer than 255 characters are truncated in the output. - Per-route settings are not reported here, see `show rules`. Listen sockets are reported by `show listen`, storages by `show storages`. ## pause Pause Odyssey execution. This will drop any session connections and pause statement executions for transaction pools. Can be used to reduce damage if you need to perform some operation on Postgres `pause` ## resume Resume Odyssey statements execution. `resume` odyssey-1.6.0-rc1/docs/features/online-restart.md000066400000000000000000000067031526064202200217430ustar00rootroot00000000000000# Online restart Sometimes you may need to upgrade Odyssey binary or make some changes in config, that is not reloaded with SIGHUP. The main idea is to do this without downtime with two Odyssey instances: the first will serve all the current connections, while the second will accept all new connections. Odyssey have several ways to perform online restart. The way of perftorming it depends on version you use. ---- ## Since 1.5.0 At version 1.5.0 nginx-like way of performing online restart was introduced. It follows the next scheme: ![Odyssey online restart](../img/online-restart.svg "Odyssey online restart") Thus, to perform online restart you will simply need: - enable reuse port for accepting from two instances: [bindwith_reuseport](../configuration/global.md#bindwith_reuseport) - send `SIGUSR2` to Odyssey instance, ex.: `kill -sUSR2 $(pidof odyssey)` ### SystemD configuration You will need Odyssey build vs `libsystemd-dev`. On ubuntu you can install it with ```bash sudo apt-get install libsystemd-dev ``` When running Odyssey under systemd, the service type must be configured correctly for online restart to work: **Recommended**: Use `Type=notify` with systemd notify support. The online restart mechanism uses `fork()` + `execve()` to spawn a new process that replaces the parent. With `Type=notify`, Odyssey explicitly tells systemd about the new main process PID, allowing seamless handoff during online restart. Example systemd service configuration: ```ini [Service] Type=notify NotifyAccess=all ExecStart=/usr/bin/odyssey /etc/odyssey/odyssey.conf Restart=on-failure ``` **Alternative** (for older Odyssey versions without systemd notify support): Use a wrapper script with `Type=simple`: ```bash #!/bin/bash # /usr/local/bin/odyssey-wrapper.sh /usr/bin/odyssey /etc/odyssey/odyssey.conf & ODYSSEY_PID=$! # Keep wrapper alive, track any odyssey process while kill -0 $ODYSSEY_PID 2>/dev/null || pgrep -x odyssey >/dev/null; do sleep 5 done ``` Then configure systemd: ```ini [Service] Type=simple ExecStart=/usr/local/bin/odyssey-wrapper.sh Restart=on-failure ``` **Note**: Unix domain sockets do not support `SO_REUSEPORT` and cannot be shared between parent and child processes. For online restart to work, you must either: - Use only TCP sockets (recommended), or - Remove Unix socket listeners from your configuration ### Disabling online restart If you don't want Odyssey to perform online restart on `SIGUSR2`, you can disable this feature with [enable_online_restart](../configuration/global.md#enable_online_restart). ### Dropping old connection It is possible to control connections dropping on old Odyssey instance. By default, one connection per second for each worker thread will be dropped between transaction blocks. But if you want to disable it and allow all the connections to work until disconnection, you can set [drop_enabled](../configuration/global.md#drop_enabled) option to `no`: ```txt online_restart_drop_options { drop_enabled no } ``` ## On 1.3, 1.4, 1.4.1 There is [enable_online_restart](../configuration/global.md#enable_online_restart) feature. Enabling it to `yes` will make Odyssey to watch if there is new another instance of Odyssey by getting lock files at [locks_dir](../configuration/global.md#locks_dir). When a new Odyssey instance is run, an old performs graceful termination. But you need to run a new version manually. You will also need enabled [bindwith_reuseport](../configuration/global.md#bindwith_reuseport). odyssey-1.6.0-rc1/docs/features/pause.md000066400000000000000000000037331526064202200201120ustar00rootroot00000000000000# Statements execution pausing *since 1.4.1* Sometimes you need to pause statements execution, ex: with Postgresql minor update. This only possible for transactional pooling because the connections to pg server will be reset anyway. To stop Odyssey execution you will need [console configured](console.md) and paused user must have [transactional pooling](pooling.md). ---- ## Configuration Lets assume you have next parts of your configuration: ```conf ... storage "local" { type "local" } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } } ... database "postgres" { user "postgres" { authentication "none" pool "transactional" storage "postgres_server" } } ... ``` ## Pausing Just execute: ```sh psql 'host=localhost port=6432 user=console dbname=console' -c 'pause' ``` ## How does it looks for client connections? Just like currently transaction finished and new transactions sleeps until Odyssey resumed. For: ```sh pgbench 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' --progress 1 ``` runs it looks like: ```plain progress: 1.0 s, 13685.2 tps, lat 0.569 ms stddev 3.550, 0 failed + psql -h localhost -p 6432 -c pause -U console -d console progress: 2.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed progress: 3.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed ... progress: 7.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed + psql -h localhost -p 6432 -c resume -U console -d console progress: 8.0 s, 4165.3 tps, lat 17.347 ms stddev 344.607, 0 failed progress: 9.0 s, 22651.8 tps, lat 0.441 ms stddev 0.066, 0 failed progress: 10.0 s, 23471.7 tps, lat 0.425 ms stddev 0.053, 0 failed ``` ## Resuming Just execute: ```sh psql 'host=localhost port=6432 user=console dbname=console' -c 'resume' ``` ## State checking Just execute: ```sh psql 'host=localhost port=6432 user=console dbname=console' -c 'show is_paused' ```odyssey-1.6.0-rc1/docs/features/pooling.md000066400000000000000000000053501526064202200204410ustar00rootroot00000000000000# Pooling with Odyssey Connection pooling is essential for efficient use of PostgreSQL in multi-user or high-load environments. Poolers manage and reuse database connections, reducing connection overhead and improving application performance. Without pooling, each client would establish its own connection, causing resource exhaustion and degraded responsiveness. Using a connection pooler ensures scalability, stability, and optimal database resource utilization. Odyssey is _really good at pooling_ and allows you to create large production setups. Pooling is controlled by rules in your configuration file. For full pooling parameters see [rules configuration](../configuration/rules.md) There are two types of pooling: session pooling and transaction pooling. ---- ## Session pooling Session pooling is appropriate when applications require a persistent database connection for each client session, maintaining session-specific settings or temporary tables. The main advantage is that each client keeps the same backend connection for the duration of their session, ensuring reliable session state. However, session pooling does not reduce the total number of connections to PostgreSQL, which may lead to scalability issues under high load. It is less efficient than transaction pooling in environments with many short-lived queries. To create session pooling for db.user write this in your configuration file: ```plaintext database "db" { user "user" { ... pool "session" } } ``` ## Transaction pooling Transaction pooling is ideal when applications do not rely on session-level features and can work with any database connection for each transaction. The main advantage is high scalability: the pooler can multiplex many client connections over a smaller number of PostgreSQL backends, greatly reducing resource usage. However, transaction pooling does not preserve session state between transactions, so features like session variables or temporary tables are not supported. This mode provides the best efficiency for stateless workloads with short transactions. To create transaction pooling for db.user write this in your configuration file: ```plaintext database "db" { user "user" { ... pool "transaction" } } ``` ### Prepared statements support As said, transaction pooling does not preserve session state between transactions, so features like session variables or temporary tables are not supported. But you can use prepared statements in transaction pooling. To create transaction pooling with prepared statements support for db.user write this in your configuration file: ```plaintext database "db" { user "user" { ... pool "transaction" pool_reserve_prepared_statement yes } } ```odyssey-1.6.0-rc1/docs/features/prometheus-metrics.md000066400000000000000000000176351526064202200226420ustar00rootroot00000000000000# Prometheus metrics This section describes ways to export Odyssey metrics in Prometheus format. ---- ## Exporter There is metrics exporter in [prometheus/exporter](https://github.com/yandex/odyssey/blob/master/prometheus/exporter/). This is an http server, that connects to Odyssey console and expose it metrics in Prometheus format on `/metrics` endpoint on specified address. To use it, you will need build and run: ```plain > go mod download && go build -o odyssey-prom-exporter > ./odyssey-prom-exporter -h usage: odyssey-prom-exporter [] Flags: -h, --[no-]help Show context-sensitive help (also try --help-long and --help-man). --odyssey.connectionString="host=localhost port=6432 user=console dbname=console sslmode=disable" Connection string for accessing Odyssey. --odyssey.scrape-timeout=5s Maximum duration for a single `/metrics` scrape (inherits the HTTP request context; use 0s to disable). --[no-]web.systemd-socket Use systemd socket activation listeners instead of port listeners (Linux only). --web.listen-address=:9876 ... Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. Examples: `:9100` or `[::1]:9100` for http, `vsock://:9100` for vsock --web.config.file="" Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md --[no-]version Show application version. ``` Currently in developing stage, so if you have any troubles with this exporter, please, [contact us](../about/contributing.md). Each `/metrics` request now inherits the HTTP context and uses `--odyssey.scrape-timeout` (default `5s`) to bound how long the exporter waits for `SHOW ...` commands. If the timeout or client cancellation fires, outstanding queries are aborted and the scrape returns `odyssey_exporter_up 0` along with any metrics collected before the interruption. ## Route-level metrics The Go-based exporter scrapes `SHOW POOLS_EXTENDED;` and `SHOW DATABASES;` and now emits **only label-based metrics** (legacy `odyssey_pool___*` names have been removed). Each route sample is keyed by the `user` and `database` labels: | Metric | Labels | Type | Description | | --- | --- | --- | --- | | `odyssey_client_pool_active_route` | `user`, `database` | Gauge | Clients currently using the route. | | `odyssey_client_pool_waiting_route` | `user`, `database` | Gauge | Clients holding the route but not attached to a backend, i.e. idle between transactions. Despite the name this is **not** a saturation signal. | | `odyssey_client_pool_queue_route` | `user`, `database` | Gauge | Clients blocked waiting for a free backend connection. Non-zero means the pool is exhausted. | | `odyssey_client_pool_maxwait_seconds_route` | `user`, `database` | Gauge | How long the longest-queued client has been waiting, in seconds. | | `odyssey_server_pool_capacity_configured_route` | `user`, `database` | Gauge | Configured `pool_size` from `SHOW DATABASES` (`0` means unlimited). When Odyssey doesn’t expose the mapping for a route, the exporter falls back to the observed `active+idle` at scrape time. | | `odyssey_route_pool_mode_info` | `user`, `database`, `mode` | Gauge | `1` for the active pool mode (`session`, `transaction`, `statement`). | | `odyssey_route_bytes_received_total` | `user`, `database` | Counter | Bytes received from clients on the route. | | `odyssey_route_bytes_sent_total` | `user`, `database` | Counter | Bytes sent to PostgreSQL backends. | | `odyssey_route_tcp_connections_total` | `user`, `database` | Counter | Client TCP connections opened toward the route. | | `odyssey_route_query_duration_seconds` | `user`, `database`, `quantile` | Gauge | Query latency quantiles (available when the `quantiles` rule option is set). | | `odyssey_route_transaction_duration_seconds` | `user`, `database`, `quantile` | Gauge | Transaction latency quantiles. | Saturation examples: ``` odyssey_server_pool_state_route{state="active"} / odyssey_server_pool_capacity_configured_route ``` If `capacity_configured_route == 0` (unlimited), prefer absolute values or derive current load directly from the unified family, for example: ``` sum by (user, database) (odyssey_server_pool_state_route{state=~"active|idle"}) ``` Clients queuing is the most direct evidence that a pool is too small — it counts clients that have work to do but cannot get a backend connection: ``` odyssey_client_pool_queue_route > 0 ``` Pair it with `odyssey_client_pool_maxwait_seconds_route` to tell a brief burst apart from sustained starvation. Note that a client that starts to queue moves out of `odyssey_client_pool_waiting_route` into `odyssey_client_pool_queue_route`, so the `waiting` series can fall exactly when saturation begins. Quantiles (`*_duration_seconds`) are instantaneous TDigest estimates; treat thresholds like gauges (for example, `odyssey_route_query_duration_seconds{quantile="0.95"} > 0.5`). ## Database-level averages Odyssey’s cron thread still computes the historical averages that fed the legacy Prometheus endpoint. The Go exporter re-exposes the most in-demand gauges: - `odyssey_database_avg_tx_per_second{database=""}` — average transactions per second observed over the most recent `stats_interval` window. - `odyssey_database_avg_query_per_second{database=""}` — same concept for queries (per-second throughput). - `odyssey_database_avg_recv_bytes_per_second{database=""}` — average bytes/sec received from clients (SHOW STATS `avg_recv`). - `odyssey_database_avg_sent_bytes_per_second{database=""}` — average bytes/sec sent to servers (SHOW STATS `avg_sent`). - `odyssey_database_avg_query_time_seconds{database=""}` — average query latency (seconds) over the stats window (SHOW STATS `avg_query_time`). - `odyssey_database_avg_xact_time_seconds{database=""}` — average transaction latency (seconds) (SHOW STATS `avg_xact_time`). - `odyssey_database_avg_wait_time_seconds{database=""}` — average wait time for server (seconds) (SHOW STATS `avg_wait_time`). ## Global lists metrics Exported from `SHOW LISTS;`: | Metric | Description | | --- | --- | | `odyssey_lists_pools` | Number of connection pools | | `odyssey_lists_used_clients` | Total connected clients | | `odyssey_lists_routing_clients` | Clients currently in routing phase (between accept and route assignment) | | `odyssey_lists_login_clients` | Clients in login/auth phase | | `odyssey_lists_free_servers` | Idle backend server connections | | `odyssey_lists_used_servers` | Active backend server connections | ## Instance metrics Exported from `SHOW INSTANCE;`, which reports the state of the Odyssey process itself. Servers that do not implement the command are skipped without failing the scrape: | Metric | Description | | --- | --- | | `odyssey_config_load_failed` | `1` if the last config load failed, leaving the process on its previous configuration | Rows of either view that the exporter has no curated name for are exported as `odyssey_lists_` or `odyssey_instance_` respectively, so a newer Odyssey can add counters without requiring an exporter release. **Alert example** for detecting routing queue buildup: ```yaml - alert: OdysseyRoutingQueueHigh expr: odyssey_lists_routing_clients > 10 for: 1m labels: severity: warning annotations: summary: "Odyssey routing queue is building up" ``` ## Error counters `SHOW ERRORS;` is exported as a single counter family: `odyssey_errors_total{type="OD_ECLIENT_READ"}`. Every error type reported by Odyssey becomes a label value, so new error codes do not require exporter changes. ## Legacy built in support Not supported anymore. See example of usage in [docker/prometheus-legacy/](https://github.com/yandex/odyssey/tree/master/docker/prometheus-legacy/) odyssey-1.6.0-rc1/docs/features/shared-pools.md000066400000000000000000000024261526064202200213730ustar00rootroot00000000000000# Shared pools support *since 1.5.1* Sometimes, you may need to limit the number of connections for several users at once. A good example is splitting routes for application connections and those from human users, such as DBAs. The latter never connect simultaneously, so it's appropriate to limit their connections to a low number to avoid consuming too much of the `max_connections` quota. You cannot achieve this with [general rules](../configuration/rules.md), as each rule has its own pool. Instead, you need a shared pool. ---- ## Configuration Suppose you have several users that you want to limit together: `db1.dba1`, `db1.dba2`, `db2.dba1`, and `db2.dba2`. You will need to define a `shared_pool` in the global section of your configuration file, then reference it by name in your rules. Here's an example: ```plain shared_pool "idm" { pool_size 3 } database "db1" { user "dba1" { shared_pool "idm" ... } user "dba2" { shared_pool "idm" ... } } database "db1" { user "dba1" { shared_pool "idm" ... } user "dba3" { shared_pool "idm" ... } } ``` With this configuration, only 3 connections are available from those routes, and all others will be waiting, as per the standard rules. odyssey-1.6.0-rc1/docs/features/soft-oom.md000066400000000000000000000032031526064202200205300ustar00rootroot00000000000000# Soft OOM support *since 1.5.1* Sometimes, even when using a connection pooler, you may encounter Out Of Memory (OOM) situations on the PostgreSQL server. In such cases, it is advisable to temporarily suspend accepting new connections until PostgreSQL’s memory consumption returns to a normal level. To address this, we have implemented a "soft OOM" mechanism, which is described in detail below. This mechanism helps prevent further overloading the database by gracefully managing new connection attempts during memory pressure events. Soft OOM makes sense only for connections that is not local, that means, that Odyssey console will still works when your host is in OOM state. ---- ## Configuration First of all you will need describe memory limitations in `soft-oom` section at the root of [configuration](../configuration/overview.md): ```plaintext # for specified process soft_oom { process 'postgres' limit 750MB } # for whole system soft_oom { limit 750MB } # top memory consumers terminating soft_oom { limit 750MB drop { signal 'SIGTERM' max_rate 2 } } ``` After that, all new connections to `db.user` will be answered by Odyssey itself process described in `soft_oom` reached limit by total memory consumption: ```bash psql 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -c 'select 42' psql: error: connection to server at "localhost" (::1), port 6432 failed: Connection refused Is the server running on that host and accepting TCP/IP connections? connection to server at "localhost" (127.0.0.1), port 6432 failed: FATAL: odyssey: cbde8d0203caf: soft out of memory ```odyssey-1.6.0-rc1/docs/features/tsa.md000066400000000000000000000061121526064202200175560ustar00rootroot00000000000000# Target session attrs *since 1.4.1* The target_session_attrs setting enables a pooler to direct client connections to PostgreSQL servers that meet specific role or session requirements, such as preferring a primary or accepting any available node. This option is useful in clustered and high-availability environments, where routing clients to the correct type of server—without modifying application code—can be important for both safety and performance. In scenarios where changing application logic is impractical, target_session_attrs offers an effective, transparent solution. In every places, where you specify target session attrs, you can specify one of the following values: - `read-write` - always select host, available for write - `read-only` - never select host, available for write - `prefer-standby` - try to select host available for write last of all - `any` (the default one) - select host randomly ---- ## You will need several hosts in your storage To allow Odyssey select host by TSA, you will need several hosts in your storage. This can be achieved by config that looks like: ```plaintext storage "postgres" { host "primary:5432,standby:5432" } ``` See [storage configuration guide](../configuration/storage.md) for more about storage section. ## TSA at listen You can specify **tsa** in listen section, to create listen ports that redirect client connections to PostgreSQL servers that meet specific role or session requirements. It looks like this in your configuration file: ```plaintext listen { host "127.0.0.1" port 6432 target_session_attrs "read-only" } listen { host "127.0.0.1" port 6433 target_session_attrs "read-write" } ``` After that, you can connect to `127.0.0.1:6432` and `127.0.0.1:6433` and will get different results for `pg_is_in_recovery()` function: ```sh $ psql -h 127.0.0.1 -p 6432 -c "select pg_is_in_recovery()" pg_is_in_recovery ------------------- f (1 row) $ psql -h 127.0.0.1 -p 6433 -c "select pg_is_in_recovery()" pg_is_in_recovery ------------------- t (1 row) ``` See [listen configuration guide](../configuration/listen.md) for more about listen section. ## TSA at rule You can specify **tsa** in rule section, to create users that redirect client connections to PostgreSQL servers that meet specific role or session requirements. It looks like this in your configuration file: ```plaintext database "db" { user "user-ro" { ... storage "postgres" target_session_attrs "read-only" } user "user-rw" { ... storage "postgres" target_session_attrs "read-write" } } ``` After that, you can connect to `db` database with `user-ro` and `user-rw` users and will get different results for `pg_is_in_recovery()` function: ```sh $ psql -h localhost -p 6432 -c "select pg_is_in_recovery()" -U user-ro -d db pg_is_in_recovery ------------------- f (1 row) $ psql -h localhost -p 6432 -c "select pg_is_in_recovery()" -U user-rw -d db pg_is_in_recovery ------------------- t (1 row) ``` See [rule configuration guide](../configuration/rules.md) for more about rules section. odyssey-1.6.0-rc1/docs/img/000077500000000000000000000000001526064202200154035ustar00rootroot00000000000000odyssey-1.6.0-rc1/docs/img/balancing.svg000066400000000000000000001667111526064202200200560ustar00rootroot00000000000000 wild clientsOdyssey asbalancerpostgresinstance 1postgresinstance 2postgresinstance 3odyssey-1.6.0-rc1/docs/img/favicon.ico000066400000000000000000007774761526064202200175600ustar00rootroot00000000000000ø (ÿ(ð à#.#. "+5?CB>:4$"3G]p�–¬ÁÐÚâêðõ÷öõóðäË©�oK,7Y<#+Ei’¹ÒãïøþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùëÕµ�iP>-‡æØ¬{c]E, *Qz¤ÉÝäê÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûõëÔ¢_&E­öÿÿÿ÷êÜ̵˜…{xfH/  .Gg‘Àçûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýã¢S% >�½çþÿÿÿÿÿÿÿÿÿøìÝξ®œ…p^M;+! *6?JZsŽ£´ÃÑÛÜÙ×ÞìøÿÿÿÿÿÿÿÿÿÿÿÿúöòíçåçìïëçãàÝØÖÖÙÝÚÓÊÀ·¯£Ÿ®ÀÒäóýÿÿÿÿÿÿÿÿÿÿúä™k3 )_©çÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþúóêâáéñõùþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôÚ´ƒ`NA9/&$'.2,'#  $;Xy˜³ÊÞîùÿÿÿÿÿÿÿÿí¹l&.w¯×ñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ÷ëÙÂ�m?1Ko™ÅïÿÿÿÿÿÿÿâœC 8b�¥²¼ÎäöÿÿÿÿÿÿÿÿÿýêßÞßÛÕÏÉÆÊÓØÔËÁ´�^E- 8�Íùÿÿÿÿÿÿó³T$Ae„•Ÿ¨¯¯ª˜a,  QŸãÿÿÿÿÿÿù»J)�Ùÿÿÿÿÿÿò ?oÏþÿÿÿÿÿð±XrÒüÿÿÿÿÿúÍt#Z©ìÿÿÿÿÿÿÝ’? 9¤óÿÿÿÿÿÿò¼eL¿ýÿÿÿÿÿÿýÖŒ?gÍÿÿÿÿÿÿÿÿòÄ|.uÛÿÿÿÿÿÿÿÿÿç›;&Œåÿÿÿÿÿÿÿÿÿî¤?,…Ùþÿÿÿÿÿÿÿÿð‹lÄøÿÿÿÿÿÿÿÿ¢Q®ñÿÿÿÿÿÿÿœCªóÿÿÿÿÿý J¸ûÿÿÿÿÿ»+)9I]sŒ¢� méÿÿÿÿÿÒ8.g’¯ÉÝéòùþÿÿÿÿfJÛÿÿÿÿÿ´ 0l´éÿÿÿÿÿÿÿÿÿÿÿÿóQ5ÆÿÿÿÿÒ'\�Âëÿÿÿÿÿ÷æÛÛßÔ½¡ˆpG ¬ÿÿÿ�V¥äýÿÿÿÿêÇŸpD' u¬|UŸÙûÿÿÿÿòÇs*  TÂúÿÿÿÿþâ«wM:;<5& M´÷ÿÿÿÿÿÿýíæìòôõõðæÖñ›„jO5 ,P£õÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûðáΰ‡_@& *X–ÌêúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþõæÐ´‘e:C�¾èýÿÿÿÿÿÿüùõôõ÷ûû÷õóñìçáÝâì÷þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòÛ¹ƒ; mÄøÿÿûïßÖÆ§ƒdRH?<>DNND>;6/' !.C[v—·ÎÖ×Ùæóýÿÿÿÿÿÿÿÿÿÿÿð³_.Ž’y{mRL6 &<X‚¹éÿÿÿÿÿÿÿÿÿÿýÖ†D -p¹ãøÿÿÿÿÿÿÿÿÿöÙ®|M'Q�n #Iy©ÑìüÿÿÿÿÿÿÿÿùæÇ“R\ôÿÿ£/T…°Ôõÿÿÿÿÿÿÿÿúܦc$ ­ÿÿÿþ„EŒÑùÿÿÿÿÿÿÿÿþà™CRk šÿÿÿÿõbvDPžáÿÿÿÿÿÿÿÿÿóÉ‘[/£µM3Ýÿÿÿÿë_]ÿß=%j³èÿÿÿÿÿÿÿÿÿýìÏ¢Yšî•&Sïÿÿÿÿðh)×ÿßR-sÁòÿÿÿÿÿÿÿÿÿÿûÒ,õâ“Evúÿÿÿÿñ`hÿÿéQ<w®Ûúÿÿÿÿÿÿÿÿÿæ‘+NÁþùÓ”W) ±ÿÿÿÿÿëS¹ÿÿà8TŸÞýÿÿÿÿÿÿÿÿã�bËÿÿüç´r9<ëÿÿÿÿÿãC"ÃÿÿÄ!b°æýÿÿÿÿÿÿÿÚ—3eÈýÿÿÿñÆo}þÿÿÿÿÿÕ/=íÿÿ—(]™ÎòÿÿÿÿÿÿÆfÂøÿÿÿÿÕ€: ›ÿÿÿÿÿÿ½šÿÿõ[:g˜ºÚûÿÈSµôÿÿÿÿñÎŒ6§ÿÿÿÿÿÿ‘.äÿÿäWX�8 D¢ìÿÿÿÿÿë¬l;¹ÿÿÿÿÿôU†ÿÿÿò} 8�ÏôÿÿÿÿÿóÕ‘<)ÒÿÿÿÿÿÓ!Æÿÿÿû˜@~½ìÿÿÿÿÿðÇštQ-Jîÿÿÿÿÿ 9âÿÿÿÿ·( 3w½ìÿÿÿÿÿÿûêÓ¹š{[(‚ÿÿÿÿÿùiXïÿÿÿÿÅ# 2l¬áýÿÿÿÿÿÿÿÿýäœI½ÿÿÿÿÿå>rúÿÿÿÿ»"#_§àøÿÿÿÿÿÿÿÿöÉ�>=æÿÿÿÿÿÉ‘ÿÿÿÿÿÈC"Ihƒ ¼Óç÷ÿÿÿÿÞ mûÿÿÿÿÿ£ ’úÿÿÿÿî®^ 'Dk�»å¸ ¡ÿÿÿÿÿûtpëÿÿÿÿÿüÒo #ÆÿÿÿÿÿëEFÌÿÿÿÿÿÿý½I;ãÿÿÿÿÿÉŒèÿÿÿÿÿÿòŸ3d÷ÿÿÿÿÿ”5¡òÿÿÿÿÿÿ怔ÿÿÿÿÿôRF²öÿÿÿÿÿÿÊW ÅÿÿÿÿÿÌ O·øÿÿÿÿÿ÷«:CìÿÿÿÿÿŠ T¹òÿÿÿÿÿë˜F‚ÿÿÿÿÿîD ?�Úþÿÿÿÿö¿V Åÿÿÿÿÿ¿jÄúÿÿÿÿø´GKñÿÿÿÿþy[Ìÿÿÿÿÿó³S ‘ÿÿÿÿÿå4“÷ÿÿÿÿÿøµ< Ñÿÿÿÿÿ­ tóÿÿÿÿÿÿçm Z÷ÿÿÿÿúc\ÜÿÿÿÿÿÿúŸ £ÿÿÿÿÿ×%+¥ùÿÿÿÿÿÿÎK,ßÿÿÿÿÿ˜ bÙÿÿÿÿÿÿï‚nüÿÿÿÿóN'™òÿÿÿÿÿÿ¶/¶ÿÿÿÿÿÇLÅÿÿÿÿÿÿÚQ=êÿÿÿÿÿ�ƒçÿÿÿÿÿîlƒÿÿÿÿÿé;0�èÿÿÿÿ÷’Èÿÿÿÿÿ´6­ûÿÿÿÿ´"Oóÿÿÿÿük ~øÿÿÿÿÁ(˜ÿÿÿÿÿÝ*�ÿÿÿÿÿÈ.%×ÿÿÿÿÿ ¨ÿÿÿÿÿÏ5cúÿÿÿÿöUªÿÿÿÿÿÖ? ¬ÿÿÿÿÿ̤ÿÿÿÿÿÞE4äÿÿÿÿÿ†žÿÿÿÿÿàExþÿÿÿÿê<‚ìÿÿÿÿÛ9Áÿÿÿÿÿ² CÈÿÿÿÿÊ Iñÿÿÿÿúd©ÿÿÿÿ¡ •ÿÿÿÿÿÖ"¦ÿÿÿûq%Øÿÿÿÿÿ�ÑÿÿÿêGhûÿÿÿÿïC`úÿÿÿÚ; ´ÿÿÿÿÿº ­ÿÿÿÿÑ)=ëÿÿÿÿül2âÿÿÿÿ¬ ˆÿÿÿÿÿÙ%vþÿÿÿù^Ïÿÿÿÿÿ‘ÃÿÿÿÿÎ[øÿÿÿÿí?Hîÿÿÿÿz ­ÿÿÿÿÿ³ …ÿÿÿÿÛ#:êÿÿÿÿúaÊÿÿÿÿ„ŠÿÿÿÿÿÒ\úÿÿÿç6Óÿÿÿÿÿ‡Òÿÿÿÿ¢aúÿÿÿÿè6˜ÿÿÿÿí8 ±ÿÿÿÿÿ§RúÿÿÿÿŠ=ìÿÿÿÿöRÒÿÿÿÿÕŽÿÿÿÿÿÃ�ÿÿÿÿü]#Øÿÿÿÿýk/èÿÿÿÿ³lýÿÿÿÿÕ£ÿÿÿÿï:Áÿÿÿÿÿ�Köÿÿÿÿ“Qöÿÿÿÿá*¿ÿÿÿÿá&«ÿÿÿÿÿ�[úÿÿÿÿv?îÿÿÿÿã*ÂÿÿÿÿÉžÿÿÿÿÿ…büÿÿÿøO7ëÿÿÿÿÝ$Ðÿÿÿÿ ›ÿÿÿÿÿ�~ÿÿÿÿÞ 7ëÿÿÿÿÞ'1ëÿÿÿüWšÿÿÿÿÿŒ·ÿÿÿÿ™4éÿÿÿÿé6kÿÿÿÿÓ�ÿÿÿÿÿ¤$àÿÿÿõC&ÚÿÿÿÿóJ ÿÿÿÿ~mýÿÿÿÿ¹Uûÿÿÿ·¿ÿÿÿÿú]Üÿÿÿà!MóÿÿÿÿÇ«ÿÿÿ÷E¤ÿÿÿÿýhvÿÿÿÿl7éÿÿÿÿÍPûÿÿÿ–’ÿÿÿÿþmEøÿÿÿÀ +áÿÿÿÿÐ?õÿÿÿâ"€ÿÿÿÿÿq2ïÿÿÿ÷FÕÿÿÿÿÒÞÿÿÿÿunþÿÿÿÿr¸ÿÿÿÿ§ÊÿÿÿÿÓ„ÿÿÿÿÑaûÿÿÿÿpRûÿÿÿí1ÁÿÿÿÿÐ+éÿÿÿüTZùÿÿÿþjÍÿÿÿÿ{½ÿÿÿÿȤÿÿÿÿ¡YúÿÿÿûZsÿÿÿÿà Âÿÿÿÿ´ F÷ÿÿÿÞbüÿÿÿòA$ãÿÿÿð6Îÿÿÿÿž ÅÿÿÿûQrÿÿÿÿê5£ÿÿÿÿo×ÿÿÿÿ�‚ÿÿÿÿŠ{ÿÿÿÿà'fÿÿÿÿ Óÿÿÿÿ|Lúÿÿÿ®jýÿÿÿÔ1îÿÿÿ¹ÃÿÿÿþlÜÿÿÿÄ VøÿÿÿËÈÿÿÿÎ ´ÿÿÿýe±ÿÿÿÖIôÿÿÿÇœÿÿÿݧÿÿÿû`‰ÿÿÿã#2äÿÿÿÂ|ÿÿÿè(zÿÿÿúXoÿÿÿè)Çÿÿÿ¹ bÿÿÿé)UöÿÿöLVýÿÿë,¬ÿÿÿªKúÿÿî1>îÿÿî:4ïÿÿî2œÿÿÿ”Öÿÿí.8ìÿÿß$¶ÿÿê+£ÿÿÿp–ÿÿè(J÷ÿÿÅ}ÿÿå%ÓÿÿøVqÿÿã"”ÿÿÿÃ{ÿÿå%Aòÿÿþv�ÿÿë-ªÿÿÿÝ&›ÿÿî0Cñÿÿÿz¥ÿÿí.«ÿÿÿÆ­ÿÿë,FòÿÿöJµÿÿè(ªÿÿÿ¢¹ÿÿâ!Cñÿÿé/·ÿÿÕ§ÿÿÿt¹ÿÿÆ 7åÿÿ¼ Àÿÿµsüÿð:¼ÿÿŸ´ÿÿŠ«ÿÿ}9èÿؤÿýXƒÿÿr°ÿï4ÑÿÑÉÿÓ[÷þhÚÿ™ ±ÿ¶0íùMHõã$^ÿÏ©ÿ`‹ÿzAôž ÀÖ–Õ9íl$Ì]ŒÁ8‚¾M:k� A ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø?ÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿ€øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÀÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿà?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿàÿü?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðð?ÿ€?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüøÿð?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø?ðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿà?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø?ÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿü?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿü?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø?ÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿodyssey-1.6.0-rc1/docs/img/odyssey.png000066400000000000000000000162621526064202200176170ustar00rootroot00000000000000‰PNG  IHDR<2glvbKGDÿÿÿ ½§“ pHYs × ×B(›xtIMEâ  ÷>å?IDATxÚíÝy˜\e�èño'éì �…% "K0a”ÝËâ **ã‚3â‚ *:×eîFÇ®¢ŒwQTÔqTYÔ½¢( ‚‚‚\�-��` „��ìéôüñ{kº¨œsªª»ºêtçûyžótºÖ·Ï[õË{Þå÷‚$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$Ie3ÆSÐ^#<RÇëihŸ‘ž©#楟ÿ <<æ)‘4\}Ø8?»qžIÃñªêãÀuÀîÀx ø¼§FÒpóÚàΫºí{ÀàÅžIÃÅ,àf`10¿êöSü©§HÒp0xG lWÓkîX œæ©’4Ôí <�ÞG2î¿"Ý÷3œŸ7¨ÿëHüïÙk€ƒ�§�»3sWú9x�§LÒP5øSjÁÝšZ{µ^˜îï¾ì)³…' UïöHÿ^,ÉxÌ’Ôú8<µ%iH9XŸZnOoÌyÜ8àŽô¸-ÀÛ=u¶ð¤¡æß‰ÉÅ÷ßÍy\/°,ý»;µòÆzú xÒPq‘ bÊÉ7S`ËÒ,ªúý@b%†$•Þ$bn]e â¾:�I¬«­<þ1à%žF[xÒPðɪÚàòž³¹êß³�Ý<�<©ì^œŒJ¿? |½Îsº€ U¿�ò’Ö€'•ÝÀ…5­³€� ¼Ú·\ð¤ŸÞGß@EÅ—xn0¥æ¶™<žTV' ª3‰? ÜÛàók[�»STdÀ“Je ÑoW{Yúï >¿—ȉWm*0ÚSkÀ“ÊæPଌۿÑÄktð xRÙMÞÆŽýmWô $àyIkÀ“Je.±ª¢Ög€íM¼NOÍïpgAžT"c�fÜ~;ðh¯“Õ‡7Êï¨O*“�33nÿ`S“¯Uðº3.seÀ“:æc·­~l` ¯Ûï¨O*‹#�S3nÿ °´É×ê%2ªÈ€'•ÒE·-®¡þR²¬€÷¬§TRÍ#² ôÖ à5ç×¼ÖFàOµ-<©ÓÞJ_6”Š[�o à5××üþ;NU‘Oj»×òüÔ DV”'𚽞VžT6GÓkn{¸z€¯[»·ÓR xRÇ�Ïó“unn£ožþèfÕÜ6Ê€gÀ“:é…Àñ<É× ÏŠRô]œ›ðüŽð¤Ž9�ç§€ê%òÝÝ3À×ÉŽ#²¶ð$uÌ"¡gõÔ‘-À)-xíqÀ¯ØqšËažvI�𙌀tO‹^{°5ãõç{Ú%µÛ±À’Œ€tf‹^VÆkð$µÝdb_ÙÚ`´´…ïqhNÀó’VR[�”s¹yn ß㸌×ßœ¡$µÅtঌ`´Œ—– Ä«2Þõ´-æ´©øûñÊtÔú­]çºKÆm.5“Ô6{›ðdõÝíÒâ÷:?ã}ÖÛ³…'µËG�·_Bd2i¥]3nÛBs›É€'õËqÄÖ‹µn¤¹ôí�˜šqÛzLeÀ“ÚàÓ9·÷'}{#fæ¼mV…OL‘qû"àfbŠJ«Í²…'©þ@ö$àO1xc¯Èx¿_ä´üd Oj‰¿ædÜþ1o0Z\c€i¶ð xR;M#VOŒÍ¸ïgÄžƒá€œÛ x*[xRÓ^,ȹïkÀÂ6•ã%d/e{Xn5I¨‘ä÷›mŽlcYnÎ)Çïpn[xR üOò×®~ x°Måì•sߟœ4ò’v"SˆÁ€¬VÕb‰Y»“‚ZVY®°ªláIõ²·]„ÈwwwËòg)×Ú×Ç�ßwð¡–ïv´èî³ê$5k ðPÎ¥l/±f¶Sö~Hvâ‚3­:IÍúû‚`·�üEûíp<1Ï®¶\Ï{[u’š1‡�È xu¸|“S®Ç¬:Iͺ¡ Ø=I~ò€vظ6§l—Yu’šñš‚`× œCgêŽ$¶Ì*ÛÉVŸ¤F� V)ä»_�=¹]F“ß·¸žì,.’”é½äO2ÞNl˜ÓI»äïY}’5‰˜Ã–׺»Šöí3›çè‚ò�nJjÔ‡ˆiYÁd‘ø³“F—¼IV¡¤FLî(&×:\ÆÉÄÆÞYå»WÐ`«n9pšU#©•>HýÑ�êJ-ÄŸÖy¯mĠɥĚYIj™ý)^+»��¥DKdR¹ØPð>›‰Ì'ÑÞµ¹’v]À-uZ\ÿ›þí-;�dø$°šâ>ºEéqS¬IƒåýôíÑš·D3ƒ#€gÛ8n¨L&öÀpSIƒêpŠw[ ¼²‰×ûsà_�ßS0âûÀ?à4Im09�¢ ô/Dÿ[=LJ7èn%²ïiHj— )NÙþÛ.3çß–4è–+4öñÔKj§Sˆ,#E—²ç<7à‹ÀJŠw «ÞÜgLÊ)©Í þã« žÿ"?ÝöÝýÀ žrI�° ‘Ö©(H= ÌÉxîë¼tíIñ[t’Ú!+ÐŒ"–†õÖ¹”}SÍóö¾KcËÀVƒÓ­Iƒel 2ûê¼xÏa=©�ËЫ �¯&v«èÖ?÷òUR+M Ö¢ “ÒÙÀÅÀwˆ¾¹ê‘×ÿ‘ž7‡È#W´~Dß¾²‰9rkê<çàæÔr4ÅºÚÆÛð³‘î|ob¥ÃÔôsFÕ±'±³XžnbIØçÓëäyˆ•í%úù>EL!É[Nv‘àvbYÚ*«KR³¦§—·¥VY½¥YYóÝ®"²üS�ǯ'öž…èûûz�Ç8$$õÃTb/‡ÿH-§M¸Êñ½ô:Ò7hp"1W®èyçT]Ê~´Îcÿ c•IjÆnD¿×ψ Àkiloí±ø ѧ7¹¦Õ5™X-QôüÏ“ÒãßBq¶ãWÛª“Ô¨‰Ä”�›ÓeêV›¼[™Àûmb9ØiD*ö‰Dÿ]^ÿíåuدSkbÑÑ2³·a?±¤#R0š¼�Ø|f õ³ün~Gd9›˜b2ºï&1‚Z4�¤²Ÿë>—Ñë�³lÙIÊ2ŽÈì{1èP¯å¶‰á|€H²ÙŠMjæ÷Öyßÿ›;žüef�'[¥’jM{ë�ª> üà‡À'€¿ha9ön¬óþ·Ñ—±äÓd§v¿5ý=’ôßvN® þvƒ‹€o#�ƒ±ôj‘ª©ÞDáS«Z‚µ«(¶ß Vl”Í"ñÁ®~ì¤öšIô±]Kì®Uo°áúV= †±)èÖ»„þçôø‘Ä2²Úû?Aÿú Ã`.p4ðVàË©uz5‘jJÒ ›NLä½­N‹îûÄ4�´gSšËvWÑ7‡î$`YÆý#;|~Iÿ‘|¸¸x0ã\ÿ±ŠDÒ ]ºþp1Ð�P¶W}r{´±lç7ì~“þˆõ¸—×ÜÿG:“r½x9Ñ—x+‘šjUÏ»qšŒÔrcÒåèœ@÷±hÿÍé2kR›Ë7·Î%u¥ïp¯ªçœÅŽ“œ�ks¹_•ÎÛ²à6ÓØ|ÄõÄDkÓOI-4‚˜§öÙkU¯þ’˜ü;ŠÎÍSûI�±"ÅÊßt*;ÎlWké àR`y*C3+K'6þ™‚s¥–éJ-µŸV]¦n%²‰\D¹:ËÏ,¸¼®$ó<²êñ³ÙqåÅ¡UÚ•èbšÌûˆ5ÂÍ.›ë!ÒS]”.Ã%µø‹úgôå€[L ¼ „e=ŒjƒÝkjZ¬©yÌuÀî-,Ó¢ïòT"�übú—aSj™~•X©"©ÅFi™¶¥/Ûe%þ²Í$F/‹æÚ�Yóœ£xþ\»«ˆ!5–˜³÷2bšK#yÇ*b…ÈÿI­QIƒäÃé w±YMYíBL^Î KÙqO €ë«.Ñ/'úbчy1p÷‚ÜŠä®$¦£Lö£( ®3ˆ &gkaËì<²—‚UZvyÁú-DR‚¯Ð7=¥Y‰}2>Ad9ÞÔ�÷1ÐòE"…üiÄfÞ’Ú` p.1áµì&« òæmœÝ•þÆþ$'x11ñFÛ¦1kêηÒy>!];iXê€n†Î`7‘å$+¨\Òâ÷šOdo¹…˜Òè<¹êãAàSÀÜÍ�š¤f�LÌc«,×3°%a»ÙŒ/~I¬ºXAó{iô�¥à{ ‘ºÞtð’äb”õWÀßÓÜbÿÊÔ›{`,'úâ¶#Ô�f`®M�ða"{ò˜|»¬& &?`Í�«®ôåÎ*­¾ƒˆ)7/%2�4ÛVé#ì!Lî~‘î©5(ðJzž R§ÿŒè›ÚVuþª—<õ¤û†Jp•Z{£‰ À/'ÖÍÎïçk®#&FV ÜåGHe`æˆÆ[+OoH_â•D_Uw ~ S‹e3°š˜ÊQY^¶1€çˆEì=m(ï" ûDbpe\ªë®ô·Œ"æ®Í&–qퟂùAý|¿íDÞˆ¹q×§À'ÙÂâæìœ¼ˆüɸÛÓ—~±\êqbzÆÓôí>¶9ÀÊ:ÒîtŒ¨¹¿úÒpkjQnM·� úÀ&¥c 1 0“˜Ê±Oú}|jÑõ¦ ØŠº_ü¸�Èåw›ð†§éD6âÉ5ªóèÛó¡Q›RàªìVVÛOV pÛÓeòætlJ?·¦@9žHa>µ ÷’¼ VKÜ<áÇA¼�Çž©å·/±÷ëQÀ±”'íy-OÁí!b~Ü"bÕÃòÔjí±êeÀÛ¹�M­¿DÆ”yéÒ÷”—ù©Ð%ú#þD_ßãštl·zeÀSžJÿÚ"{ðÅÄâùNùc h‹ÒÏGˆ­×Ð×7X9$žd4ðúVÙJŒ�¡±-×}iO#ÊKˆ,(O¦Û*ƒ³ÖÓJ< êyCjñÍ-¨‡íÄF:ß'¦Ã,O�­ÌŸë%¦½¬$2$©´&CìjV™®R´ëwÀ{ˆþÁñþ‡%Ùª^DìÊu 1½eOò÷¯&ö_½�A}6ݶ&M ?£‰A° ©®ǾVÞ0q"ðJbÁþ>Ä$â±�_JL®LY•.‡+«+6¤`¸ŠØ·b]:6{ª‡„]ˆ}„ßGL"_DlRôM†Î2FžêÚ•˜Ô<—È*2/3›|�íÄÒ·§ˆ ÇϦà÷'àZba¿Êib#¥³jn_BdÛ¾ÝS¤áj±ºã=)P dó›Êq®©.«£�Ÿ“Ÿ­ú�ž"í,f ˆu½_"²¸<Þ�€·”X«rùK¢6¯ÞV›¾« þÏ>t=�Žû€kè[‹;ŠØƒb&1ÙyO¢ßg�ôïYD†”Š+ˆþ=•ÇÙÀ§)Nqÿcà‡ž*)t+=*söºSP¬¬ü¨,á©*�iÀ×ê´È·ßaèì©"Iÿ­’»ðˆô¢`·‰Ø�I’†”îtÉz6;n°”u¬Þëi“4”Œ%¦�O̧kd`i ðzO�¤¡b2ð2à“D2‡FGÑW²ã@dÀÙÔ�@÷"‰ÄTO¥¤2{5±oÝuDRØI¸Æ]R‰ü¨An#ðUà…ôe¶‘¤ÒA¬TùF®'¹_gRœòK’JèvÞßÄ¥ë:à1b}ìÞžBIC!ÐÍL-³ûlÍ-~ œkkNÒP0‰˜ò!"Ùf½@·¸¸”È](I¥7økàÛDfèz�î9b¤õ�Øßu^ÏÛžÎÿµÄ>° ýJð†cKmrºd9888dˆþ=ËR|8ÿ¬'fýoJw¸¶núöïèNÿ1N¬s=-Ý^ÏúôÈ5ÀçˆÝàdÀVvö!FÛÞJl€<œ= Ü�Žß§Ë´Õé˾9µ*·—´ìRkm|jéŽK-ßYÀœtìG (ìÞàkn#¦Ÿ,"¦—|-� ð†…±é‹±oº$=89}yZ©öòt}Õejoj…LH-ËÑé—nk¤o©•-ÁûÒeð3DZñ ©¸!µ{Òg¦² Ú¨tÛ¶tTÿ{{Õï#«žWY…°¥êØZõsszNå¼T‚ÛäÔZÛ3u#ÌJÝ{+ Æôão®ìËû‘åä&à6Âo89xiºÌ™—.U'öóËRÙv Ñ�Vݧ¶¦êgÖ¿7¦ 0&¶ñ)�Kå™BäD›–¾ì“ªŽ‰ô-Uê­ *ˆuž3úi�(ÂÛRÐÚZ·Ð— ¤;=nk  ›ÒyØœþ½9ý¾µê¼LNÇÔôs 6¦–í""›ð⪖î*Ã�o8Ùx‘\qA�Ç®$§ËœµU_ä éòoEº\]Õ‚«›¡ücj.áFW¼ž„Ʀ€X –sRëu1ز³º¸žè¿\šêÔ‘qÞ°õàãôeªXž.aV¦Ȫô?ý)¸mI-ŽçªZÛ«.Ó†J'w ~»}Yóˆ©3óÒ1i×ù=À‰¦@·Ý¯¼o8›I¬m<9}ào!r‘=C_ŸTuŸRÏ0?£R+pL:¦¤`86ý~0ŸèÛ,»žª`¶<÷¿I] ëS=K;EÀ«t”wU6ÿ—ßñuU}*sÖÆ¦à· Žӱ/ѯØnËR0»?µÎïžäù« ¨j‰K;í%­Zÿ¹™IŒ–NOǬg§û¦ƒ+#jjíg®·êge è.àÁÔÅðà^¢oT2à©ÔF}ˆ»ý‰Õ›ÔT¦­l$ž2°I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$µÞ‚õ¦G¸LIEND®B`‚odyssey-1.6.0-rc1/docs/img/odyssey.svg000066400000000000000000000335011526064202200176250ustar00rootroot00000000000000 odyssey-1.6.0-rc1/docs/img/online-restart.svg000066400000000000000000001314411526064202200210760ustar00rootroot00000000000000 eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1daXPiyLL9Pr+C8PvSN16jqX25XHUwMDExXHUwMDEzN7CNXHLeXHLt9fZcclx1MDAwN1x1MDAwNmFcdTAwMGJjQYO84In57y9L2JJAi1x1MDAwMavb3DdousdtbVx1MDAxNFWZJ09mZWX9+VuhsOaN+vbaP1x1MDAwYmv2c7PRdVqDxtPaV3P+0Vx1MDAxZVxmnZ5cdTAwMGKXiP/7sPcwaPp33npef/jP339cdTAwMGafsJq9+/FTdte+t11vXGL3/Vx1MDAxYn4vXHUwMDE0/vT/XHUwMDBmV5yWefbxW7Emd582ldpoNVx1MDAxZW7dTq/4eO0/6t/01pjGYNB7XG5PP8M5jLSyOMWEXHUwMDA3p0fmNFaWZlJQrCjXVIZXn5yWd2vusIiWXFxQzoiQnFJGgltubefm1vNfrizEmVCSI/+gwT1ccvema9qEgjNDb9C7szd63d7AtPV/sG3+XHUwMDBiW3vdaN7dXGZ6XHUwMDBmbiu8p+1cdTAwMWbhPW2n2617I//N0LXQjWtT7z97a/7U+bSn4Fx1MDAwM29uXXs4nHim1280XHUwMDFkz+8oXHUwMDE0flx1MDAwM9O6frXlj9J/wjZcclx1MDAxYfd21Vxmk/vQ7Vx1MDAwNqdcdTAwMWS3ZT/7g4InPs1tvX7a21x1MDAxMIfjR17P/Fx1MDAxNbbdts2LXHRiXFwwTGU4XHUwMDA2oZxhzabPXHUwMDFl9FxcX+ZcdTAwMTSG4cNEhTc4w02QNc9/a7vRXHUwMDFk2mH3m6aVQzmc+DJcdTAwMGb9VmP8XHUwMDEwloJcdTAwMTOBhMBIhK3pOu7d9DPdXvMu4XP6PScq6OZcYv9VXGLlxf8l+Pd/vibeXUyXUnPE5TN84W9TL17rNobeRu/+3vHgm1x1MDAxZZlWTn+joddcdTAwMTh46zCwjnszfc12W+GVyNC+arYvXHUwMDFma9vceV7/MTq4c9o7W27nSpzfnpXXwlx1MDAxNq+1e81cdTAwMDfTN0VkYa5cdTAwMTGhWChNXHUwMDExwVx1MDAwNEfuumn04Vx1MDAxZWFhjVx1MDAxOYxcdTAwMDKPi41paMmAwa3diEkmNDV6bVx1MDAxYTXs7nXvKVx1MDAxY7jfXCKvnkKlSu2pdiFrnf7L9jOnvVv8snlN46jk2c/eXHUwMDE0KJmBoYRcdTAwMTGs5Fx1MDAxNC4xYlGqXGJcdTAwMTdpuERcdTAwMDS1XHUwMDE011JiSVx1MDAxNMdcXMRhKYJ1K1x1MDAxOFxuYIgkw9CkhI/xhkkmQOhY+JlcdTAwMTG4XHRFcVx1MDAxYW5cYoyrZIjjcEzywlx1MDAxYnit0OFr58BcdTAwMWJf+uD791qj4dBcdTAwMWVcdTAwMTV+t73m76+/gFx1MDAwMXajw9pzvbrz4n9cdTAwMTU0cXarce90zTjwifeWus6N6ZO1rt2OiDh0i+eAkVx1MDAwZi57vX54XHUwMDE1PtNrOK49iFx1MDAwZlZv4Nw4bqP7bfY2N1x1MDAxZbxezVx1MDAxZY5b7VxyXHUwMDFl7GhcdTAwMWbZlcBSW68wkazJ2cCUocmaXHUwMDEzK0mPXHSlXHUwMDAwT2CwOFfJekxcdTAwMTlcdTAwMDNN14pFL65UOFOF6ewqTDCC3sc4ootRysCnz77psJBcZlx1MDAwNiXKNGZX4dAyh1x1MDAwNjBcdTAwMTCx8m2JPVx1MDAxZNpb2L08XHUwMDFmNdGPfrPs9Fwiti9GY4Mrf33Nem82Nc58b4xcdTAwMDIk01x1MDAxY1x1MDAxY1WpeWHn4GRvr/DHXHUwMDFmhVx1MDAxYuhB9/HL4ebFVfWgUq5Vv5U3/7HE0DNju/OBn2zZeMe90Vx1MDAxNkIwWoJcYixx6ID4KCSxhSVcdTAwMDOGTH02wUJcIvxcdTAwMDZDyEJUafT6NEJwV1x1MDAxY41cdTAwMDS2uETGiVx1MDAxYdPIUKdW4Fx1MDAxNIBcdTAwMTP7uJuDKVNCa1x1MDAxMnNoTFx1MDAwYkVcZsneMFx1MDAwYmvNuGTqZ/k5XHUwMDBiXHUwMDAxQL5+ToaYmiMmoOFcdTAwMGJjIDe3n/Nhb1x1MDAwNpxcdTAwMTlglOCiccpcdFx1MDAwMe7OXCKNf/VmMLG0Qr6rXHUwMDE2XHUwMDEzj1x03yre0p/v6LjnvaHTdTdv5Mbx6EBcdTAwMTZcdTAwMGZcYjk8n4lcdTAwMWUpaXFcIulcdTAwMDTJXHUwMDE5+excdTAwMDdGlHGiXHUwMDE0SuFHMNJcdTAwMTY2plx1MDAxOEPHaaFWLOmtTe9cdTAwMDFcdTAwMTGfnSWZmFx1MDAwMZOUJ5EkRadPhn5cdTAwMGXSUnHCcydJ9vnwrnnbL7GSc4BrJ/3Dzd2Tm5zIXGZcdTAwMDBcdTAwMTmiXCK0blx1MDAwYpCZobnsfflH4X9cdTAwMGLX0N/+P7rOXHUwMDEwXHUwMDE4wpdl5jKzNTtcdTAwMWYqkz2CmVRcdTAwMDZcdTAwMTi7xZFUbyhcdTAwMWVKl49cdTAwMThSWppjTt9cdTAwMTAjlNkwZEuVkPg12kWIXGbtYVx1MDAwMFx1MDAxOVpaXHUwMDA0+aaCaXOEt6xcdTAwMTAkQFx1MDAxMPFhKqM5Q1pcdTAwMTGUxGREalx1MDAwMFx1MDAwNVx1MDAwYkXhYCpvXHUwMDFlM9Z9XHUwMDE1lejPitemiag5YsL5K2hMtnGfpDFIgXZcdTAwMDItRIoxLXCk8Vx1MDAxMVx1MDAxYZNcdTAwMTiS/XRcdTAwMGVTWX+8uHa2S+7zSXG0f39w2ilcdTAwMTbrs1x1MDAwNWsxII8kxsWaXCIxmlmc6cC9SiAxXHUwMDAwaVx1MDAxMkiMooykRWt5qK4rXGJcbiBIzk5iMOKacFx1MDAwNpKZXHUwMDAwNlxcp4JccsNcZsilQrl7TSAxoMFhXHUwMDE3LMA0XHUwMDFhzabd90BbXG5g593hd7fhtlxuQ9srXHUwMDFjXm3sXHUwMDFkls/LXHUwMDFiS8w35ml8PqzjpLu7d6TJydUueUZDx72oXHUwMDBmqu5MrENzZDHBVMxDYaDcXHUwMDE0iC54mylcdTAwMWVcbiYq/mig1siiROG5PJQ2Uk2E/lx1MDAwNsqtPswvoGOxXHUwMDAyI8STdJ7FTlx1MDAwNjrPJVwiguSv8mC1iVx1MDAxNDQqvJ9CMJJk0lx1MDAxY8U3ccyTUaRPXHUwMDAwf4aB3+tcZi87+1x1MDAxYuxsvdOuuD36uH/3sD9TkEJw63UmeVx1MDAxMlx1MDAwMTi3XHUwMDEwWHah08w7QIRcdTAwMDCaXHUwMDA2V8cokeBvrEJcdTAwMTRJXHUwMDAwoOeYyFGCXHUwMDEykuxJ0HTjbkj2RCQ1t4ioXHUwMDE2TH4oilCvbp/Ua2SJXHJ4rIX5WOnLw1x1MDAxM+Ztjk7Pr/iWOv+xV76r7STkSyTHXHUwMDA2mDVth/1+IcZ8SyxSrTS1XGJOs9FcdTAwMWNra1x1MDAxYzZefiVtNYa39q/V0tLHzTSHzmfg0yZcdTAwMDVcdTAwMTilTFdfXHUwMDBltp1qmX8mhcmtwfjzXHJ1XFwszVx1MDAxMVxu5P9fO71+zCr8oHi1Vy2Vry7uW2eHnVx1MDAxYZrNTssgejJcdTAwMDFcdTAwMDJcdTAwMTJpS1xiwmRqslx1MDAwNbhjlpJaaYUkUCSN/kuzpj5cdTAwMDFcdTAwMDTW5zDV4C1hKnUkflx1MDAxNyXlqfNcdFx1MDAwMO5cblOF8ELzXHTpXHUwMDExf5BcdTAwMGLEXHUwMDE3I+VBxN+DQSuMXHUwMDFk2uWO8ac0NFx1MDAxZst9vV282DpcdTAwMWPd3zjeXHUwMDBmPrr3jjv948OZgmcgXHUwMDEywcTmJLtW2OJEi3TLrbklgV1jzLRJXHUwMDA1ZSt2/dqm91R2e47YmaJUUopIkspm2GdJhVRKsEX4deZcZqDYOFx1MDAxZlx1MDAxY4lR+6WFxNXG0UuZ71x1MDAxN8PxmJC2uWdcdTAwMDDB9oOj9qEsyiFcYl7DbXXtwTJjQUIj88GB7NHJZvCaWFIhgHnCfHWeRFx1MDAwM00tnYlcdTAwMDbUwFx1MDAwMSPAXHUwMDBiJVx1MDAxM5hcdTAwMTCdYMLNXHUwMDE0IVxc5GDkXHUwMDA1ZpHQ71xuXHUwMDFjXHUwMDAycKjMXHUwMDBlXHUwMDBlXG48K8GTMyjBXHUwMDBmTlx1MDAwNVx1MDAwN3C7NV2IumdcdTAwMThzXHUwMDA1ZFlHolx1MDAwMJ9G3FPlMIfHp1x1MDAwNThPLyBt/i/btlx1MDAxNyZcdTAwMTZlmGlcdTAwMTZFXGLV2vxcZt2oQmT+XHUwMDBm9Fx1MDAxN3RcdTAwMTgpivy5/OiLnOewqVx1MDAxM1FLXHUwMDBigaOoXHSGb078WZmJPrWY5jpy4Fi3pMwzpn7lhqCEXHUwMDE1v1x1MDAxZJw594/tY695VN5cdTAwMTFpX1x1MDAxOYRcdTAwMGV4JFx1MDAwM+xcdTAwMTJcdTAwMDRMXuwr81m+IbIoZ1LDX4G0UJxPrNMx6W1i8pBcdTAwMTnfMSdHbcJcbvgtr9s3iU60/4HVYb1vN51GN+FcdTAwMDOnr2WYkEu5PqxtlkT9alNsuy+1yn3pabZ5WOhBK5ZcdTAwMGI2XHUwMDBl1CpcdTAwMGJOYqAyfo5g3Hhw6Vx1MDAwN2rB85/0XHUwMDFlV0Qy01ZU58q3Z6AhNPKh0dzVVGPBXHUwMDAwXHUwMDAzwXFX4WN5TcIyLqn80CRsuze4W2qnb7qBOTl7meiYpaHM5JtcdTAwMGKGKNbKrIpcdEfd71x1MDAxYkmN/rJpbzBgeFx1MDAxNFlSamBcdTAwMWR4ejpmpaeZero7h8NcdTAwMDeGXGZAMJHUqbjyXHUwMDA2KZ9cYjGGpVwii6Rm/SyH72vWe1x1MDAxZr1cdTAwMTd2LJ6+NcpcdTAwMDdcYlx1MDAxZlx1MDAxNrc7/dNNNNt7Y1xcK5mLfiiwNLGg5I/va32nVei1XHUwMDBi/cZcdTAwMDA67fvaXHUwMDEyg87MLc9cdTAwMDeNsscx0+Xkilx1MDAxOTZcboCkXHUwMDA1pURNslx1MDAwNoGy4VxiW0An4FmigI1hpkPJXHUwMDBm0Ehxa85cdTAwMTSPv1xyJO3NXHUwMDBlSZqCx1x1MDAwM72cxFx1MDAxY4hIXamHXHUwMDExVlx1MDAxOEaH5E5cdTAwMWSWxdVMlUBzvMpezE34XHUwMDE5XHUwMDFl4qzuXHUwMDEyeDmYKlx0fUdcYphcdTAwMTnF4+tcXD4rM/TXOzqnXHUwMDFi5XZla3/nh1x1MDAxYVx1MDAxYzpccufsgPf1aEZHR1mxOJhcdTAwMGZZXHUwMDFhXHUwMDAzoLFYPD1wc4yfQ3VqTtpcbqqSoGp/XHUwMDBl9iRBrinByV5cdTAwMGVNpU+YgcuP1E9YMdPj+PSG17ePr90qbaHrzc42Oc+J5lx1MDAxMCklZ1x1MDAxZoqX289289H+krqQvzDJJizLWmZ/60PfJlx1MDAxZjqUPd7v0CGTzzZdXHRJXHUwMDEyZUlwwSlmbLJOUrhEmMupXHUwMDEyXHUwMDA3IVx1MDAwM2KWpFNotLTQ8lx0s+dcdTAwMDezY1x1MDAwYifQ+Vx1MDAxY3hqXHUwMDAytMRn1Fx1MDAwM2TBSFNcZvwp90w30H4wI5+/9jcufuaIXGLer6BA2cZ8glx1MDAwMkFLqWJAXFyVXHUwMDEySmiZsNZcdTAwMTesOFxc4Ej76/aXcLHMQa2rL693beE+9vfrndJcdTAwMDPH9btcdTAwMTlRRlwii1A1mfvuXHUwMDAzjWRwQTCOsE5cdTAwMDZcdTAwMWEgLSZMm1x1MDAwMjVcdTAwMDCMq3z6ZJQ5nIPBXGKEXHSlKDFxPrI6NpZPXHUwMDBifrJcdTAwMDRcdTAwMDOxXGKByYZcdTAwMTlccj/lQiQj36V5SdJnjle5y1x1MDAxM2WWKyHvxe4xdLhxO7wn7dp2X1VcdTAwMGaLh1x1MDAxNzM5Klx1MDAwNCWYf1/XKVx1MDAwN1x1MDAxYUOQ5Gm6TjmxONWSUjWd0rdyVzKV/XiOXHR88Dik+ZPkrZD0VTKagFx1MDAxZYBpyp9TaFx1MDAxOG+6UN3ESPb8RmVvs+C0XHUwMDBi9rNcdTAwMDMvL3xpN5wu/PR6XHUwMDA1X2OW2YOYq/X5eFxmxeNcbqtcZm469Y2dfdK23dO7s7OEtXFJ0zlcdTAwMDRcdTAwMWNcdTAwMDaiXHUwMDEzZlxcXHUwMDE1MbEpmaXfhFmIa3jQZFxuTFx1MDAxOO6Vfmfqd22eyVx1MDAxY8VcdTAwMTiSyT5cdTAwMDPhqevsOVx1MDAxMmaNuVxcKEUnMIRcdNGI/avnq1x1MDAxYm/r8qx1XFyVdKfqdFx1MDAxZdfrXHUwMDExmlx1MDAxY+OOwZVcdTAwMTmy94B+kI9FI76vgUv+fe2/sVx1MDAxZdnsTc9cdTAwMDc1skfynThcdTAwMDNOXHUwMDBlX8IpS6tY0cNgsXw8uz9cXKlD48VcdTAwMTJXmFx1MDAxMWDGyVx1MDAxY4lcdTAwMWGaw5/krD6VSlx0tFk5wVx1MDAxMc1/Pc7iWp0r/Y9cdTAwMGKfOVwiYpcn/0+LMmSb6kI0yoCYXHRcdTAwMTZJxkwhJkJJZG7oLcpALVx1MDAwMThP3upcZr9eX6IoXHUwMDAzv9i4u1T6mJapeDiTh5rVvYSq7kkzJFx1MDAxMiCGyXhJXHUwMDBljVx1MDAwMWIkXHUwMDE2UidOkDCmLaAsXHUwMDEyM8o5W1x1MDAxNVx1MDAxNZtcdTAwMWRhTudAXHUwMDE4iZhmPHmOJL3yKuNcblHK8l9ScFwiRy3dOGg2LvjR8+7Oy9Hdzc56TqREKS6iiydyLipWeHK820KtfFIvXHUwMDE3tvZK20tMUFx1MDAxNvtcdTAwMWE5lf/IXHUwMDFj4XfIXG6xiFRcdTAwMDCnTGih8WTRMTCUlkKKXHUwMDEwyanJLE2qn4qVwuDnYPM0lpFVbuEkibT8upWvVZ9wqKIrgFx0XHUwMDAw5myeRUumao9OjmHieDHDsLYzZvJnLCpWSlCFl6C8WLo0miMmh7+C02RcdTAwMWL5SU4jMIG2m3hcIjdxh4TlXHUwMDA1yFJUcEN6YJxcdFC25WM1taNcdTAwMTe7tbu1ZZe3yHbluVhcdTAwMTLoUs7EaoDIWWKauozVi2PLLF3TgqRkuGOq/Vx1MDAxNHdcdTAwMDRAxU1cdTAwMTXFXHUwMDE1sVx1MDAxObfpPdw5n1x1MDAwM3cwIFx1MDAwZnBLkVjVMHV5szLLeTDWuUdbXHUwMDFlXiotvMlcdTAwMGZbp5164/Fmq7+3tzeajdh8zXrvXHUwMDFlPnrq2m57R1x1MDAwZXvFq1x1MDAwZek36uckJ8JEXHUwMDE10zSSKLNITlx09O/IuzX1xXp3/1piSpTW0HxIT/boZ5NcdTAwMWXAUTGd7jHWJyHAQeKEq7SlNFRcdTAwMDBQ6ynXK8BcdTAwMTjwOTmhc4Zp/jbztFx1MDAxN3NgXHJcdTAwMDFElzBcYklcdTAwMWNHpe54ZYpVYkD/hUK72YFcdTAwMWFcclxmmOioqH5cdTAwMGXHSVx1MDAxMsDxhUD2flx1MDAwNa/JNvNcdTAwMTFeY9ZcdTAwMTCamVBGieDaXHUwMDE0otXxRYSYWGii7UtEaCp3vFZqlErNM7dX4dUu2umcPs1EaFx1MDAxMKOW2YSFKqCbTKnJ3SmA1Vx1MDAxOM6nNUtlNatcdTAwMTJcdTAwMTBcdTAwMGJcIs3lXHUwMDFjeWdcdTAwMDBcdTAwMTgwXHUwMDEyiUCDUSqrYTBkZlov94zWg92iqlZ5qeiWf1xcn2+J2n6tndfCXHUwMDFkXG7eXHUwMDA1/1hl1r95XHUwMDA1iN7mxWN5b3RGXHUwMDFi+OLo/KbiXGaOjiozgVx1MDAwMTH5X1x0VZRBxrBlymemc1x1MDAwZVx1MDAxMFBolVl0XHUwMDFlyzZZ4UAmXHUwMDBlNObAXHUwMDAxTVx1MDAxMFwiKjGokpErQoFuXGJcdTAwMWRpaW6F2lx1MDAxOMboo6lcIt/Ktf0l1tNYXHUwMDBi81HSbFx1MDAwN+7dJHEwx1xcYMU4YHyYXHUwMDExOJZIJS1KUrM+TLlcdTAwMDRwLEBHOUfwXHUwMDBlzFx1MDAxMsKhkphqXHUwMDA3k1x1MDAwYnxXKlx1MDAxYqjs9ewqK7RJ0k7epTI9Y1xcaWoq7Yjcd1lcdTAwMDBia/ap/HRcdTAwMTchQ1xuzVx1MDAxMZG/pXJcdTAwMTSMWzPeyVx1MDAwYlx1MDAxMcQ0pjLBT0BcdTAwMTZcdTAwMGLY8LveQlqTSOfifnRY3uyUt15qlZfu8IfTXHUwMDE5pvouTHElpFx0t3OWsFx1MDAwZq+2lGmw1sLsyvVpm/FWW6Pa3d5G6dqT+mmPlop357o2XHUwMDEzMeFYWVx0U8lcdTAwMThwzqJUTNdcIlxiSFx0Jybialx1MDAxNlx1MDAxOL/WK1iRknGb3kO45uxcYmeKSWpcdTAwMTKNnUZIXHRNTVc3lWC45Pnv4oleblx1MDAxZvDGxkH9rFxc3y6d7HpusXJcdTAwMWPRnFx1MDAwZjknWlx1MDAxMfWx3Tandl5YYuaT2tJ8XHUwMDE4ULZcdTAwMTP5TqlpbZl9e4nCXHUwMDE48Di0klx1MDAwMVx1MDAwMUJaxbPWw7JcdTAwMDHg0Fx1MDAwMHNcIlx1MDAwNJtlS1x1MDAxNCXELFx1MDAwMDnUKpktXHJcdTAwMWVas8NcdTAwMDNcdTAwMDWzI0Xy/nUkvo1mWL5Sga8p8q9cdTAwMWNAlWaSLFRcdTAwMWQ+58pcdTAwMDFpQmiOUPx+XHUwMDA1/8lcdTAwMGVcdTAwMWZOklxy6pfxXHUwMDEyQHE0XHUwMDAzVSOJ+2RcIlM9722JWTZcdTAwMDH6jHDpt+pz52Gwd37cXd9/6ZxubXXXS62ZiFxiXCJmeT+OM1x1MDAxMcxcdTAwMDSQrCzQ0cTSPFx0llZUJFx1MDAxM2tu58k6MZWtSGKl3PTEWSCGXHUwMDE0XHUwMDE4Pcu/cFKp2Lnc3DxcdTAwMWQ4pHV14N1dXnZQsZtcdTAwMWJcdTAwMTXh6IOztDeDRtNuP3RcdTAwMGKePbhcdTAwMDfL75meWl4+kt3cfEhJttuVuVx1MDAxMkdLi2JcdTAwMTHfo1x1MDAwNmOhLUKxMkvAaXJaPVGmXG6SwpIjfy3OXG5cdTAwMWLGbXpcdTAwMGZcdTAwMWKcOVJeXHUwMDExwC5cdTAwMTE8kYhgkl79XHUwMDEwiMhk0cTP91O+Zr33p6aG+OEjXHUwMDFlVa15Qce7tU32aWMwKjw1hoW3T1he2HmvwflcdTAwMDBPtqnI9oYwtmLbaoyRRzFLXHUwMDEz8LNjmSSR3FikXHUwMDEzXHUwMDBiXHUwMDExMbMl+tRTK+RcdJCnM0dcYliaXHUwMDEy/lx1MDAxYyWyktR6RIxcdTAwMGIz05Z/loiCg5GFZm1yXHUwMDBlXHUwMDAxT4qeOVwiQlx1MDAxNz5cdTAwMWVDpdz8nmw/oDBcdTAwMTH3lUhpSUz2pplcdTAwMDZcdTAwMTVcdHFfXG43aSaklEuaJdKpXHUwMDBlznf06cnTI1x1MDAxZu6fNc+HLm1vzERukFx1MDAxNJac3qPL1yRw8ixcbo6PklwiZcJJXGJT8FxmLlxuTFerjOeAmLvZIVx1MDAwNlx1MDAxY1x1MDAxZsWkUDSJ3NCMXX2UqWOOXHUwMDE2WtDzXHUwMDFlyGiuPliXzPG+oGVe8Fx1MDAxYmthPlQgm1x1MDAxOL5bTlx1MDAxNVSSgzZy8GnR5Fx1MDAwZVx1MDAxZZggacE9wexwKFx1MDAxOJHZYY4pyJG/04eMXHUwMDE2glxiq/yYjf6mp5dXelx1MDAxYuhtd1x1MDAwZb3lnPj73SZxXHUwMDAzitL376BcdTAwMTJRM+H3XHUwMDEzJojNXGbH57ODYrokmiMqg7+CKcw8XHUwMDFkXHUwMDBizWaIUoklM9tcdTAwMThyJlwiXHUwMDA0J1xmkJraLZj6gUBcdTAwMDZ8IZsrpDUqez61MFx1MDAxMbY1y6rAy+VmtpSauvvxVlx1MDAwMXRwoDfydf1RrE2/iKV4T3v1Y3S8Vf1Rc1x1MDAxZsq6UyNtvTlcdTAwMWL8IYUtNr1cdTAwMTOwX+xIXHUwMDExXHUwMDBiSySloGnhWTNtL1J8IS7NYm49Z1x0xb9Nzvz9XHUwMDFjgOdcdTAwMTNklrIuUKZOXHUwMDA3MYqBUkRcdTAwMTdcdTAwMGXmRlSA2C+24Vi+eJcggOaIiF6eOLdcXOXNdkv8pldcdTAwMTa6Ut687LNWzW5Uz1x1MDAxMoJcdTAwMWZxx1x1MDAwNETCiqn0uI6Jskz+lETT+8lcdTAwMDRcbm/WKGBcclx1MDAwNFiPISMh6MpD1Vjxm0Dd3TnUnSrMXHUwMDA0Sd5uVKb7JVxmrD4z+Up5q7vEWEVcdTAwMGLWL+CXXHUwMDE4XHUwMDA3wXFcdTAwMWbswlNvcFx1MDAwNzrz3e25hV639Vx1MDAxYSRcXGJ/ZeaWz+3H/PZcbkZrjX6/7kGPXHUwMDA3rGXt0bGf1pNVoWkrXHUwMDEzfPYxwaiCbYbvz79+++v/XHUwMDAw7Vx1MDAwN/lKIn0=odyssey /etc/odyssey.confNULL == getenv(ODY_INHERITED)socket() + bind() + listen()accepting connsand set O_CLOEXECSIGUSR2still accept()sighandlerfork()ODY_INHERITED="pid of parent"execve(odyssey /etc/odyssey.conf ODY_INHERITED=...)SIGCHLD if exited (failed to start)"..." == getenv(ODY_INHERITED)socket() + bind() + listen() with REUSE FLAGeverything ok?sighandlerSIGTERMaccepting connsgraceful terminationthe binary was updatedexit(0)continue workingon old binaryodyssey-1.6.0-rc1/docs/index.md000066400000000000000000000046761526064202200162750ustar00rootroot00000000000000# Welcome to Odyssey --- ## What is Odyssey



Advanced multi-threaded PostgreSQL connection pooler and request router. Odyssey is production-ready, it is being used in large production setups. We appreciate any kind of feedback and contribution to the project. ## Why Odyssey ### Multi-threaded processing Odyssey can significantly scale processing performance by specifying a number of additional worker threads. Each worker thread is responsible for authentication and proxying client-to-server and server-to-client requests. All worker threads are sharing global server connection pools. Multi-threaded design plays important role in `SSL/TLS` performance. ### Advanced transactional pooling Odyssey tracks current transaction state and in case of unexpected client disconnection can emit automatic `Cancel` connection and do `Rollback` of abandoned transaction, before putting server connection back to the server pool for reuse. Additionally, last server connection owner client is remembered to reduce a need for setting up client options on each client-to-server assignment. ### Better pooling control Odyssey allows to define connection pools as a pair of `Database` and `User`. Each defined pool can have separate authentication, pooling mode and limits settings. ### Authentication Odyssey has full-featured `SSL/TLS` support and common authentication methods like: `md5` and `clear text` both for client and server authentication. Odyssey supports PAM & LDAP authentication, this methods operates similarly to `clear text` auth except that it uses PAM/LDAP to validate user name/password pairs. PAM optionally checks the connected remote host name or IP address. Additionally it allows to block each pool user separately. ### Architecture and internals Odyssey has sophisticated asynchronous multi-threaded architecture which is driven by custom made coroutine engine: [machinarium](https://github.com/yandex/odyssey/tree/master/sources/machinarium). Main idea behind coroutine design is to make event-driven asynchronous applications to look and feel like being written in synchronous-procedural manner instead of using traditional callback approach. One of the main goal was to make code base understandable for new developers and to make an architecture easily extensible for future development. More information: [Architecture and internals](development/internals.md).odyssey-1.6.0-rc1/docs/nginx.conf000066400000000000000000000060021526064202200166170ustar00rootroot00000000000000worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 2048; multi_accept on; use epoll; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server_tokens off; client_body_buffer_size 1K; client_header_buffer_size 1k; client_max_body_size 1m; large_client_header_buffers 2 1k; client_body_timeout 10; client_header_timeout 10; send_timeout 10; reset_timedout_connection on; gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; gzip_disable "msie6"; limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=strict:10m rate=1r/s; limit_conn_zone $binary_remote_addr zone=addr:10m; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; server { listen 80; server_name pg-odyssey.tech; access_log /var/log/nginx/http_access.log; if ($http_user_agent ~* (bot|crawler|spider|scraper)) { return 403; } return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name pg-odyssey.tech; ssl_certificate /etc/certificate.pem; ssl_certificate_key /etc/certificate.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; root /usr/share/nginx/html; index index.html; limit_req zone=general burst=20 nodelay; limit_conn addr 10; location / { try_files $uri $uri/ /index.html; } } } odyssey-1.6.0-rc1/docs/quick-start.md000066400000000000000000000264021526064202200174240ustar00rootroot00000000000000# Quick start of Odyssey usage There are several ways to start using Odyssey: - debian and ubuntu packages from PGDG - pre-built public docker image - local build of Docker image - local build --- ## Debian and Ubuntu packages Odyssey is available via PGDG repository. Learn more about the repository here: https://wiki.postgresql.org/wiki/Apt To use the package, add PGDG: ```bash $ sudo apt install -y postgresql-common ca-certificates $ sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh ``` And then install Odyssey: ```bash $ sudo apt-get install -y odyssey ``` The package will create simple config at `/etc/odyssey/odyssey.conf` and systemd service, that can be controlled with: ```bash $ sudo systemctl start odyssey $ sudo systemctl stop odyssey $ sudo systemctl reload odyssey $ sudo systemctl status odyssey ``` *Note: new releases can be available at repository after some time, day or two after the release on Github.* --- ## Pre-built Docker image The easiest way to get started with Odyssey is to use our pre-built Docker images from GitHub Container Registry. Only version starts with `1.5.0` are supported. ### Quick start with default configuration There are public images based on alpine on `ghcr.io/yandex/odyssey`. You can try it with: ```bash $ docker run -d \ --name odyssey \ -e PG_HOST=your-postgres-host \ -e DB_NAME=mydb \ -e USER_NAME=myuser \ -e USER_AUTH_TYPE=none \ --network host \ ghcr.io/yandex/odyssey:1.5.0-rc2 ``` Or with full config mount to `/etc/odyssey/odyssey.conf`: ```bash $ docker run -d \ --name odyssey \ -v ./config.conf:/etc/odyssey/odyssey.conf \ --network host \ ghcr.io/yandex/odyssey:1.5.0-rc2 ``` This will run Odyssey on port 6432, that is connected to `your-postgres-host`. ```bash $ psql 'host=localhost port=6432 user=myuser dbname=mydb' -c 'select 42 as the_value' the_value ----------- 42 (1 row) ``` ### Available image tags - latest - Latest stable release - edge - Latest development build from master branch - 1.5.0 - Specific stable version - 1.5.0-rc1 - Release candidate - 1.5 - Latest patch version in 1.5.x series - 1 - Latest minor version in 1.x series ### Environment Variables Reference #### Global Settings | Variable | Default | Description | |----------|---------|-------------| | `DAEMONIZE` | `no` | Run as daemon (`yes`/`no`) | | `SEQUENTIAL_ROUTING` | `no` | Enable sequential routing (`yes`/`no`) | | `LOG_FORMAT` | `%p %t %l [%i %s] (%c) %m` | Log message format | | `LOG_DEBUG` | `no` | Enable debug logging (`yes`/`no`) | | `LOG_CONFIG` | `no` | Log configuration on startup (`yes`/`no`) | | `LOG_SESSION` | `yes` | Log client sessions (`yes`/`no`) | | `LOG_QUERY` | `no` | Log SQL queries (`yes`/`no`) | | `LOG_STATS` | `yes` | Log statistics (`yes`/`no`) | | `STATS_INTERVAL` | `10` | Statistics logging interval (seconds) | | `WORKERS` | `"auto"` | Number of worker threads | | `RESOLVERS` | `1` | Number of DNS resolver threads | | `READAHEAD` | `4096` | Socket readahead buffer size (bytes) | | `CACHE_COROUTINE` | `1024` | Coroutine cache size | | `NODELAY` | `yes` | Enable TCP_NODELAY (`yes`/`no`) | | `KEEPALIVE` | `15` | TCP keepalive interval (seconds) | | `KEEPALIVE_KEEP_INTERVAL` | `5` | TCP keepalive probe interval (seconds) | | `KEEPALIVE_PROBES` | `3` | Number of TCP keepalive probes | | `KEEPALIVE_USR_TIMEOUT` | `0` | TCP user timeout (seconds, 0 = disabled) | | `COROUTINE_STACK_SIZE` | `8` | Coroutine stack size (KB) | | `CLIENT_MAX` | `20000` | Maximum client connections | | `CLIENT_MAX_ROUTING` | `0` | Maximum routing operations per client (0 = unlimited) | | `SERVER_LOGIN_RETRY` | `1` | Number of server login retry attempts | | `HBA_FILE` | - | Path to HBA (host-based authentication) file | | `GRACEFUL_DIE_ON_ERRORS` | `no` | Gracefully shutdown on errors (`yes`/`no`) | | `GRACEFUL_SHUTDOWN_TIMEOUT_MS` | `30000` | Graceful shutdown timeout (milliseconds) | | `AVAILABILITY_ZONE` | - | Availability zone identifier | | `ENABLE_ONLINE_RESTART` | `yes` | Enable online restart support (`yes`/`no`) | | `BINDWITH_REUSEPORT` | `yes` | Enable SO_REUSEPORT (`yes`/`no`) | | `MAX_SIGTERMS_TO_DIE` | `3` | Number of SIGTERM signals before forced shutdown | | `ENABLE_HOST_WATCHER` | `no` | Enable host watcher (`yes`/`no`) | #### Listen Configuration | Variable | Default | Description | |----------|---------|-------------| | `LISTEN_HOST` | `0.0.0.0` | Listen address (`*`, `0.0.0.0`, specific IP) | | `LISTEN_PORT` | `6432` | Listen port | | `BACKLOG` | `16` | TCP listen backlog size | | `LISTEN_TLS_MODE` | `disable` | TLS mode (`disable`, `allow`, `require`, `verify_ca`, `verify_full`) | | `LISTEN_TLS_CERT_FILE` | - | Path to TLS certificate file | | `LISTEN_TLS_KEY_FILE` | - | Path to TLS private key file | | `LISTEN_TLS_CA_FILE` | - | Path to TLS CA certificate file | | `LISTEN_TLS_PROTOCOLS` | `tlsv1.2` | TLS protocol versions | | `LISTEN_CLIENT_LOGIN_TIMEOUT` | `15000` | Client login timeout (milliseconds) | | `LISTEN_TARGET_SESSION_ATTRS` | `any` | Target session attributes (`any`, `read-write`, `read-only`) | #### Storage Configuration | Variable | Default | Description | |----------|---------|-------------| | `STORAGE_NAME` | `postgres_server` | Storage name identifier | | `PG_HOST` | `127.0.0.1` | PostgreSQL server host | | `PG_PORT` | `5432` | PostgreSQL server port | | `STORAGE_TLS_MODE` | `disable` | Storage TLS mode (`disable`, `allow`, `require`, `verify_ca`, `verify_full`) | | `STORAGE_TLS_CERT_FILE` | - | Path to storage TLS certificate file | | `STORAGE_TLS_KEY_FILE` | - | Path to storage TLS private key file | | `STORAGE_TLS_CA_FILE` | - | Path to storage TLS CA certificate file | | `STORAGE_TLS_PROTOCOLS` | `tlsv1.2` | Storage TLS protocol versions | | `ENDPOINTS_STATUS_POLL_INTERVAL` | `1000` | Endpoints status polling interval (milliseconds) | | `SERVER_MAX_ROUTING` | `0` | Maximum routing operations per server (0 = unlimited) | #### Database and User Configuration | Variable | Default | Description | |----------|---------|-------------| | `DB_NAME` | `default` | Database name (use `"dbname"` for quoted names) | | `USER_NAME` | `default` | Database user name (use `"username"` for quoted names) | | `USER_AUTH_TYPE` | `clear_text` | Authentication type (`none`, `clear_text`, `md5`, `scram-sha-256`, `cert`) | | `USER_PASSWORD` | `password` | Database user password | | `POOL_TYPE` | `session` | Connection pool type (`session` or `transaction`) | | `POOL_SIZE` | `128` | Maximum pool size | | `MIN_POOL_SIZE` | `0` | Minimum pool size (0 = disabled) | | `POOL_TIMEOUT` | `0` | Pool timeout (milliseconds, 0 = unlimited) | | `POOL_TTL` | `0` | Connection time-to-live (seconds, 0 = unlimited) | | `POOL_DISCARD` | `no` | Discard connection after use (`yes`/`no`) | | `POOL_SMART_DISCARD` | `no` | Smart discard based on connection state (`yes`/`no`) | | `POOL_CANCEL` | `no` | Enable query cancellation (`yes`/`no`) | | `POOL_ROLLBACK` | `yes` | Automatic rollback on connection return (`yes`/`no`) | | `CLIENT_FWD_ERROR` | `yes` | Forward server errors to client (`yes`/`no`) | | `APPLICATION_NAME_ADD_HOST` | `no` | Add client host to application_name (`yes`/`no`) | | `RESERVE_SESSION_SERVER_CONNECTION` | `no` | Reserve server connection for session (`yes`/`no`) | | `SERVER_LIFETIME` | `3600` | Server connection lifetime (seconds) | | `POOL_CLIENT_IDLE_TIMEOUT` | `0` | Client idle timeout (milliseconds, 0 = unlimited) | | `POOL_IDLE_IN_TRANSACTION_TIMEOUT` | `0` | Idle in transaction timeout (milliseconds, 0 = unlimited) | | `POOL_RESERVE_PREPARED_STATEMENT` | `yes` | Reserve prepared statements (`yes`/`no`) | | `USER_LOG_DEBUG` | `no` | Enable debug logging for this user (`yes`/`no`) | | `MAINTAIN_PARAMS` | `no` | Maintain connection parameters (`yes`/`no`) | | `USER_TARGET_SESSION_ATTRS` | `any` | User-level target session attributes | | `QUANTILES` | - | Query latency quantiles to track (e.g., `"0.99"`) | #### Virtual Database (Console) Configuration | Variable | Default | Description | |----------|---------|-------------| | `VDB_STORAGE_NAME` | `local` | Virtual database storage name | | `VIRTUAL_DB_NAME` | `"console"` | Virtual database name for admin console | | `VIRTUAL_DB_USER_NAME` | `"console"` | Virtual database user name | | `VIRTUAL_USER_AUTH_TYPE` | `none` | Virtual user authentication type | | `VIRTUAL_USER_PASSWORD` | - | Virtual user password | #### Advanced Configuration | Variable | Default | Description | |----------|---------|-------------| | `PRE_INCLUDE_PATH` | - | Path to configuration file to include before main config | | `POST_INCLUDE_PATH` | - | Path to configuration file to include after main config | | `RUN_MODE` | `production` | Runtime mode (`production`, `test`, `debug`, `root`) | #### Runtime Mode The `RUN_MODE` variable controls how Odyssey runs: - **`production`** (default): Runs as unprivileged `odyssey` user - **`test`**: Runs as root (for testing with `/proc` access) - **`debug`**: Runs as root with debug settings - **`root`**: Runs as root **Warning:** `test`, `debug`, and `root` modes should only be used in testing environments! --- ## Docker build After that you can simply do: ```bash make quickstart ``` This will create and run `odyssey` as `odyssey`, with base config from `docker/quickstart/config.conf`. This configuration writes logs to the stdout, so to view the logs, you can do: ```bash docker logs odyssey ``` ### Build with your config You can start odyssey with your config. You need to do: ```bash docker build -f docker/quickstart/Dockerfile . --tag=odyssey ``` There are two ways to achieve this. The first method is to modify the default configuration. Simply add new parameters to the "docker run" command: ```bash docker run -e LISTEN_PORT=1234 \ --rm \ --name "odyssey" \ odyssey ``` You can find other settings with default values that you can modify below: ```bash LOS_SESSION=yes LOS_QUERY=no LISTEN_HOST="*" LISTEN_PORT=6432 PG_HOST="127.0.0.1" PG_PORT=5432 DB_NAME=default USER_NAME=default USER_AUTH_TYPE="clear_text" USER_PASSWORD="password" POOL_TYPE="session" POOL_SIZE=1 VIRTUAL_DB_NAME="console" VIRTUAL_DB_USER_NAME="console" ``` Another way is to use volume to start with your own config file. Simply replace PATH_TO_YOUR_CONFIG with the path to your configuration file and run this command: ```bash docker run -d \ --rm \ --name "odyssey" \ -v PATH_TO_YOUR_CONFIG:/etc/odyssey/odyssey.conf \ odyssey ``` ## Local build Currently Odyssey runs only on Linux. Supported platforms are x86/x86_64. To make minimal build on ubuntu you will need: ```bash sudo apt-get install openssl postgresql-server-dev-all build-essential cmake ``` Optional dependencies: ```bash # For systemd notify support (online restart) sudo apt-get install libsystemd-dev ``` After that you can simply do: ```bash make build_release ``` This will create executable `odyssey` in `build/sources`, which can be run with config like: ```bash build/sources/odyssey config.conf ``` Config can be as simple as: ```conf daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no coroutine_stack_size 24 listen { host "0.0.0.0" port 6432 } storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } ``` odyssey-1.6.0-rc1/docs/tf/000077500000000000000000000000001526064202200152405ustar00rootroot00000000000000odyssey-1.6.0-rc1/docs/tf/terraform.tfvars.example000066400000000000000000000002031526064202200221150ustar00rootroot00000000000000folder_id = "folder-id" domain = "my.odyssey.web.domain" # with editor/admin role for storages sa_id = "service-account-id"odyssey-1.6.0-rc1/docs/tf/web.tf000066400000000000000000000035711526064202200163560ustar00rootroot00000000000000variable "folder_id" {} variable "sa_id" {} variable "domain" {} terraform { required_providers { yandex = { source = "yandex-cloud/yandex" version = ">= 0.47.0" } } } provider "yandex" { folder_id = var.folder_id } resource "yandex_iam_service_account_static_access_key" "sa-static-key" { service_account_id = var.sa_id description = "static access key for object storage" } resource "yandex_storage_bucket" "odyssey-web" { access_key = yandex_iam_service_account_static_access_key.sa-static-key.access_key secret_key = yandex_iam_service_account_static_access_key.sa-static-key.secret_key bucket = var.domain max_size = 1073741824 acl = "public-read" website { index_document = "index.html" error_document = "404.html" } https { certificate_id = data.yandex_cm_certificate.example.id } } resource "yandex_cm_certificate" "le-certificate" { name = "my-le-cert" domains = ["${var.domain}"] managed { challenge_type = "DNS_CNAME" } } resource "yandex_dns_recordset" "validation-record" { zone_id = yandex_dns_zone.zone1.id name = yandex_cm_certificate.le-certificate.challenges[0].dns_name type = yandex_cm_certificate.le-certificate.challenges[0].dns_type data = [yandex_cm_certificate.le-certificate.challenges[0].dns_value] ttl = 600 } data "yandex_cm_certificate" "example" { depends_on = [yandex_dns_recordset.validation-record] certificate_id = yandex_cm_certificate.le-certificate.id #wait_validation = true } resource "yandex_dns_zone" "zone1" { name = "odyssey-zone-1" description = "Public zone" zone = "${var.domain}." public = true } resource "yandex_dns_recordset" "rs2" { zone_id = yandex_dns_zone.zone1.id name = "${var.domain}." type = "ANAME" ttl = 600 data = ["${var.domain}.website.yandexcloud.net"] } odyssey-1.6.0-rc1/mkdocs.yml000066400000000000000000000102441526064202200157030ustar00rootroot00000000000000site_name: Odyssey site_description: Documentation for Odyssey site_author: Yandex Cloud MDB site_url: "" repo_url: https://github.com/yandex/odyssey repo_name: yandex/odyssey edit_uri: blob/master/docs/ use_directory_urls: false theme: name: material palette: primary: 'indigo' accent: 'indigo' features: - announce.dismiss - content.action.edit - content.action.view - content.code.annotate - content.code.copy - content.tooltips - navigation.footer - navigation.indexes - navigation.sections - navigation.tabs - navigation.top - navigation.tracking - search.highlight - search.share - search.suggest - toc.follow palette: - media: "(prefers-color-scheme)" toggle: icon: material/link name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default primary: indigo accent: indigo toggle: icon: material/toggle-switch name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: black accent: indigo toggle: icon: material/toggle-switch-off name: Switch to system preference font: text: Roboto code: Roboto Mono favicon: img/favicon.ico icon: logo: logo copyright: Copyright © 2018 YANDEX LLC, Privacy Policy, Maintained by the Yandex Cloud MDB team. plugins: - search: separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' - minify: minify_html: true - tags - exclude: glob: - "Dockerfile" - "tf/*" - offline markdown_extensions: - abbr - admonition - attr_list - def_list - footnotes - md_in_html - toc: permalink: true - pymdownx.arithmatex: generic: true - pymdownx.betterem: smart_enable: all - pymdownx.caret - pymdownx.details - pymdownx.emoji: emoji_generator: !!python/name:material.extensions.emoji.to_svg emoji_index: !!python/name:material.extensions.emoji.twemoji - pymdownx.highlight: anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.keys - pymdownx.magiclink: normalize_issue_symbols: true repo_url_shorthand: true user: squidfunk repo: mkdocs-material - pymdownx.mark - pymdownx.smartsymbols - pymdownx.snippets: auto_append: - includes/mkdocs.md - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true combine_header_slug: true slugify: !!python/object/apply:pymdownx.slugs.slugify kwds: case: lower - pymdownx.tasklist: custom_checkbox: true - pymdownx.tilde nav: - Home: index.md - Quick start: quick-start.md - Configuration: - Overview: configuration/overview.md - Global parameters: configuration/global.md - Listen: configuration/listen.md - Storage: configuration/storage.md - Rules: configuration/rules.md - Soft OOM: configuration/soft-oom.md - Shared pools: configuration/shared-pools.md - Features: - Pooling: features/pooling.md - Balancing: features/balancing.md - Target session attrs: features/tsa.md - Online restart: features/online-restart.md - Catchup timeout: features/catchup-timeout.md - Pause: features/pause.md - Prometheus metrics: features/prometheus-metrics.md - Console: features/console.md - Soft OOM: features/soft-oom.md - Shared pools: features/shared-pools.md - Development: - Running things locally: development/local-runs.md - Internal structures: development/internals.md - Debugging: development/debugging.md - Packaging: development/packaging.md - Testing: development/testing.md - About: - Community: about/community.md - Contributing: about/contributing.md - License: about/license.md - Release notes: about/release-notes.md odyssey-1.6.0-rc1/modules/000077500000000000000000000000001526064202200153475ustar00rootroot00000000000000odyssey-1.6.0-rc1/modules/Makefile000066400000000000000000000003421526064202200170060ustar00rootroot00000000000000PATH_SO=/tmp/od_modules/audit_module.so SO_DIR=/tmp/od_modules init: mkdir -p ${SO_DIR} compile_all: init gcc -shared -o ${PATH_SO} -I../sources -I../third_party/machinarium/sources -I../third_party/kiwi/ audit_module.c odyssey-1.6.0-rc1/modules/audit_module.c000066400000000000000000000023161526064202200201700ustar00rootroot00000000000000/* * Odyssey module. * * Scalable PostgreSQL connection pooler. */ #include "audit_module.h" od_module_t od_module = { .module_init_cb = audit_init_cb, .auth_complete_cb = audit_auth_complete_cb, .auth_attempt_cb = audit_auth_attempt_cb, .disconnect_cb = audit_disconnect_cb, .unload_cb = audit_auth_unload, .config_init_cb = audit_config_init, }; int audit_auth_attempt_cb(od_client_t *c) { return OD_MODULE_CB_OK_RETCODE; } int audit_auth_complete_cb(od_client_t *c, bool auth_ok) { FILE *fptr; fptr = fopen("/tmp/audit_usr.log", "ae"); if (fptr == NULL) { printf("Error!"); return OD_MODULE_CB_FAIL_RETCODE; } if (auth_ok) { fprintf(fptr, "usr successfully logged in: usrname = %s", c->startup.user.value); } else { fprintf(fptr, "usr failed to log in: usrname = %s", c->startup.user.value); } fclose(fptr); return OD_MODULE_CB_OK_RETCODE; } int audit_disconnect_cb(od_client_t *c, od_status_t s) { return OD_MODULE_CB_OK_RETCODE; } int audit_init_cb() { return OD_MODULE_CB_OK_RETCODE; } int audit_auth_unload(void) { return OD_MODULE_CB_OK_RETCODE; } int audit_config_init(od_rule_t *rule, od_config_reader_t *cr, od_token_t *token) { return OD_MODULE_CB_OK_RETCODE; } odyssey-1.6.0-rc1/modules/audit_module.h000066400000000000000000000006211526064202200201720ustar00rootroot00000000000000#pragma once /* * Odyssey module. * * Scalable PostgreSQL connection pooler. */ #include "module.h" int audit_auth_attempt_cb(od_client_t *c); int audit_auth_complete_cb(od_client_t *c, bool auth_ok); int audit_disconnect_cb(od_client_t *c, od_status_t s); int audit_config_init(od_rule_t *rule, od_config_reader_t *cr, od_token_t *token); int audit_auth_unload(); int audit_init_cb(); odyssey-1.6.0-rc1/odyssey.conf000066400000000000000000000415611526064202200162540ustar00rootroot00000000000000### ### Odyssey configuration file. ### # # Include files. # # Include one or more configuration files. Include files can # include other files. # # include "path" # ### ### SERVICE ### # # Start as a daemon. # # By default Odyssey does not run as a daemon. Set to 'yes' to enable. # daemonize no # # Process priority. # # Set Odyssey parent process and threads priority. # # priority -10 # # Pid file. # # If pid_file is specified, Odyssey will write its process id to # the specified file at startup. # # pid_file "/var/run/odyssey.pid" # # # UNIX socket directory. # # If unix_socket_dir is specified, Odyssey will enable UNIX socket # communications. Specified directory path will be used for # searching socket files. # unix_socket_dir "/tmp" # # UNIX socket file mode. # # Set `unix_socket_mode` file mode to any created unix files. # unix_socket_mode "0644" # # Directory to place odyssey internal-used locks # Odyssey will use /tmp by default # locks_dir "/tmp/odyssey" # # In this mode odyssey will perform gracefully shutdown # when signalled with SIGUSR2: # it will not terminate established connections, but do not accept new ones # graceful_die_on_errors no # # Timeout for graceful shutdown in milliseconds # Default:30000 (30 seconds), use 0 to disable # graceful_shutdown_timeout_ms 30000 # # Online restart feature. # When setting to yes, restart odyssey simply with # running new version (old one will automatically perform graceful shutdown) # enable_online_restart no # # Online restart feature. # This section can be used to configure connections dropping during # online restart # online_restart_drop_options { # When set to yes - connections to old odyssey instance # will be dropped by rate of one per sec for each worker. # When set to no - odyssey will work with connections # on old instance until it disconnect. # Default: yes drop_enabled no } # # UNIX socker SO_REUSEPORT optional enable # bindwith_reuseport no ### ### LOGGING ### # # Log file. # # If log_file is specified, Odyssey will additionally use it to write # log events. # # log_file "/var/log/odyssey.log" # # # Log text format. # # Odyssey allows to configure log text format. This could be useful to # support external log parser format. Format string can contain plain # text, escape symbols and format flags. # # Supported flags: # # %n = unixtime # %t = timestamp with date # %p = process ID # %i = client ID # %s = server ID # %u = user name # %d = database name # %c = context # %l = level (error, warning, debug) # %m = message # %M = message tskv # %r = client port # %h = client host # log_format "%p %t %l [%i %s] (%c) %m\n" # # Log to stdout. # # Set to 'yes' if you need to additionally display log output in stdout. # Enabled by default. # log_to_stdout yes # # Log to system logger. # # To enable syslog(3) usage, set log_syslog to 'yes'. Additionally set # log_syslog_ident and log_syslog_facility. # log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" # # Verbose logging. # # Enable verbose logging of all events, which will generate a log of # detailed information useful for development or testing. # # It is also possible to enable verbose logging for specific users # (see routes section). # log_debug no # # Log configuration. # # Write configuration to the log during start and config reload. # log_config yes # # Log session events. # # Write client connect and disconnect events to the log. # log_session yes # # Log client queries. # # Write client queries text to the log. Disabled by default. # log_query no # # Log client statistics. # # Periodically display information about active routes. # log_stats yes # # Statistics update interval. # # Set interval in seconds for internal statistics update and log report. # stats_interval 60 # # Log stats in Prometheus format. # # Write stats in Prometheus format in addition to ordinary logs. # # Enable log general odyssey's log, not related to concrete routes # log_general_stats_prom no # # Enable log route related logs # Note, this option also turn on general prometheus logging log_route_stats_prom no # # Port for Prometheus metrics http-server to listen # Require Prometheus C library and Promhttp library # Once port is set and server is started change require restart # # promhttp_server_port 7777 ### ### PERFORMANCE ### # # Worker threads. # # Set size of thread pool used for client processing. # # 1: By default, Odyssey runs with a single worker. This is a special # mode optimized for general use. This mode also made to reduce multi-thread # communication overhead. # # N: Add additional worker threads, if your server experience heavy load, # especially using TLS setup. # workers 1 # # Resolver threads. # # Number of threads used for DNS resolving. This value can be increased, if # your server experience a big number of connecting clients. # resolvers 1 # # IO Readahead. # # Set size of per-connection buffer used for io readahead operations. # readahead 8192 # # Coroutine cache size. # # Set pool size of free coroutines cache. It is a good idea to set # this value to a sum of max clients plus server connections. Please note, that # each coroutine consumes around `coroutine_stack_size` of memory. # # Set to zero, to disable coroutine cache. # cache_coroutine 0 # # Coroutine stack size. # # Set coroutine stack size in pages. In some rare cases # it might be necessary to make stack size bigger. Actual stack will be # allocated as (`coroutine_stack_size` + 1_guard_page) * page_size. # Guard page is used to track stack overflows. # # 16KB by default. # coroutine_stack_size 8 # # TCP nodelay. # # Set to 'yes', to enable nodelay. # nodelay yes # # TCP keepalive time. # # Set to zero, to disable keepalive. # keepalive 15 # # TCP keepalive interval # keepalive_keep_interval 75 # # TCP keepalive probes # keepalive_probes 9 # # TCP user timeout # keepalive_usr_timeout 0 # # Timeout for connection to backend in ms # backend_connect_timeout_ms 20000 # # Maximum SIGTERM count before hard exit # max_sigterms_to_die 3 ### ### GLOBAL LIMITS ### # # Global limit of client connections. # # Comment 'client_max' to disable the limit. On client limit reach, Odyssey will # reply with 'too many connections'. # # client_max 100 # # Global limit of client connections concurrently being routed. # Client connection is being routed after it is accepted and until it's startup # message is read and connection is assigned route to the database. Most of the # routing time is occupied with TLS handshake. # # Unset or zero 'client_max_routing' will set it's value equal to 64 * workers # # client_max_routing 32 # # If server responds with "Too many clients" client will wait for server_login_retry milliseconds. # # server_login_retry # # 1 by default. ### ### LISTEN ### # # Listen section defines listening servers used for accepting # incoming client connections. # # It is possible to define several Listen sections. Odyssey will listen on # every specified address port and can use separate TLS settings. # # Odyssey will fail in case it could not bind on any resolved address. # listen { # # Bind address. # # If host is not set, Odyssey will try to listen using UNIX socket if # unix_socket_dir is set. # host "*" # # Listen port. port 6432 # # TCP listen backlog. backlog 128 # # TLS support. # # Supported TLS modes: # # "disable" - disable TLS protocol # "allow" - switch to TLS protocol on request # "require" - TLS clients only # "verify_ca" - require valid client certificate # "verify_full" - require valid client certificate # # tls "disable" # tls_ca_file "" # tls_key_file "" # tls_cert_file "" # tls_protocols "" # # Target session attrs feature. Odyssey will lookup for primary/standby # for connection on this listen, depending on value set. # Possible values are: # * read-write - always select host, available for write # * read-only - never select host, available for write # * any (the default one) - select host randomly # # Odyssey will traverse hosts of storage and execute pg_is_in_recovery against them, to check if # host is primary or not. # # target_session_attrs "read-write" # # balancing # Override the load balancing behaviour for this listen. When not set, # the storage-level balancing section is used. See the balancing docs # for available methods and options. # # balancing { # method "roundrobin" { # az_aware no # } # show_notice_messages yes # } # # # Support of PostgreSQL protocol compression (experimental). Set to 'yes' to enable, disabled by default. # compression no # client_login_timeout # Prevent client stall during routing for more that client_login_timeout milliseconds. # Defaults to 15000. } ### ### ROUTING ### # # Odyssey allows to define client routing rules by specifying # 'database', 'user' and 'storage' sections. # # On client accept appropriate route is assigned by matching 'database' and # 'user' sections, all requests then forwarded to a 'storage' # (which is referenced from the 'user' section). # # Database | default. # # Defines database name requested by client. Each 'database' section structure # consist of a 'user' subsections. # # A special 'database default' is used, in case when no database is matched. # # User | default. # # Defines authentication, pooling and storage settings for # requested route. # # A special 'user default' is used, in case when no user is matched. # # Storage . # # Defines server used as a data storage or admin console operations. # storage "postgres_server" { # # Storage type. # # "remote" - PostgreSQL server # "local" - Odyssey (admin console) # type "remote" # # Remote server address. # # If host is not set, Odyssey will try to connect using UNIX socket if # unix_socket_dir is set. # Multiple hosts may be specified, separate with comma. Port should be specified # using [] braces host "[localhost]:5432,host1,[192.168.1.1]:5433" # # Default remote server port. Odyssey will use this port for remote host connection # if port was not specified using [] braces. # port 5432 # # If target_session_attrs is set (from listen or query), Odyssey will check every endpoint attrs # not more often than endpoints_status_poll_interval milliseconds within one conn # Default 1000 # endpoints_status_poll_interval 1000 # # Remote server TLS settings. # # tls "disable" # tls_ca_file "" # tls_key_file "" # tls_cert_file "" # tls_protocols "" # # Global limit of server connections concurrently being routed. # We are opening no more than server_max_routing server connections concurrently. # # Unset or zero 'server_max_routing' will set it's value equal to number of workers # # server_max_routing 4 # Storage lag-polling watchdog # # Defines storage lag-polling watchdog options and actually enables cron-like # watchdog for this storage. This routine will execute `watchdog_lag_query` against # storage server and send return value to all routes, to decide, if connecting is desirable # with particular lag value. watchdog { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 pool_timeout 0 pool_ttl 1201 log_debug no # Watchdog will execute this query to get underlying server lag. # Consider something like now() - pg_last_xact_replay_timestamp() or # git@github.com:man-brain/repl_mon.git for production usages watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM NOW())) - 100" watchdog_lag_interval 10 } } database default { user default { # # Authentication method. # # "none" - authentication turned off # "block" - block this user # "clear_text" - PostgreSQL clear text authentication # "md5" - PostgreSQL md5 authentication # "scram-sha-256" - PostgreSQL scram-sha-256 authentication # "cert" - Compare client certificate Common Name against auth_common_name's # authentication "none" # # Authentication certificate CN. # # Specify common names to check for "cert" authentication method. # If there are more then one common name is defined, all of them # will be checked until match. # # Set 'default' to check for current user. # # auth_common_name default # auth_common_name "test" # # Authentication method password. # # Depending on selected method, password can be in plain text or md5 hash. # # password "" # # Authentication query. # # Use selected 'auth_query_db' and 'auth_query_user' to match a route. # Use matched route server to send 'auth_query' to get username and password needed # to authenticate a client. # # auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" # auth_query_db "" # auth_query_user "" # Authentication PAM. # # auth_pam_service "passwd" # # Client connections limit. # # Comment 'client_max' to disable the limit. On client limit reach, Odyssey will # reply with 'too many connections'. # # client_max 100 # User parameters maintenance # # By default, odyssey saves parameters values defined by user # and deploys them on server attach, if they are different from servers. # This options disable feature. # # maintain_params no # # # Target session attrs feature. Odyssey will lookup for primary/standby # for connections of this user, depending on value set. # Possible values are: # * read-write - always select host, available for write # * read-only - never select host, available for write # * any (the default one) - select host randomly # # target_session_attrs "read-write" # # # Remote server to use. # # By default route database and user names are used as connection # parameters to remote server. It is possible to override this values # by specifying 'storage_db' and 'storage_user'. Remote server password # can be set using 'storage_password' field. # storage "postgres_server" # storage_db "database" # storage_user "test" # storage_password "test" # # Remote server auth # password_passthrough "yes" # By default odyssey authenticate users itself, but if side auth application is used, # like LDAP server, PAM module, or custom auth module, sometimes, # instead of configuring storage_password, it is more convenient to reuse # client-provided password to perform backend auth. If you set this option to "yes" # Odyssey will store client token and use when new server connection is Opened. Anyway, if # you configure storage_password for route, password_passthrough is essentially ignored # # # Server pool mode. # # "session" - assign server connection to a client until it disconnects # "transaction" - assign server connection to a client during a transaction lifetime # pool "transaction" # # Server pool size. # # Keep the number of servers in the pool as much as 'pool_size'. # Clients are put in a wait queue, when all servers are busy. # # Set to zero to disable the limit. # pool_size 0 # # Server pool wait timeout. # # Time to wait in milliseconds for an available server. # Disconnect client on timeout reach. # # Set to zero to disable. # pool_timeout 0 # # Server pool idle timeout. # # Close an server connection when it becomes idle for 'pool_ttl' seconds. # # Set to zero to disable. # pool_ttl 60 # # Server pool parameters discard. # # Execute 'DISCARD ALL' and reset client parameters before using server # from the pool. # pool_discard no # # Server pool auto-cancel. # # Start additional Cancel connection in case if server left with # executing query. Close connection otherwise. # pool_cancel yes # # Server pool auto-rollback. # # Execute 'ROLLBACK' if server left in active transaction. # Close connection otherwise. # pool_rollback yes # # drop stale client connection after this much seconds of idleness, which is not in transaction. 0 means inf (never drop) # pool_client_idle_timeout 0 # # drop client connection in transaction after this much seconds of idleness. 0 means inf (never drop) # pool_idle_in_transaction_timeout 0 # # Forward PostgreSQL errors during remote server connection. # client_fwd_error yes # # Add client host name to application_name parameter # application_name_add_host yes # # Connect new client to server immediately or wait for first query # reserve_session_server_connection yes # # Server lifetime - maximum number of seconds for a server connection to live. Prevents cache bloat. # Server connection is deallocated only in idle state. # Defaults to 3600 (1 hour) # Use 0 to disable # server_lifetime 3600 # # Enable verbose mode for a specific route only. # log_debug no # Compute quantiles of query and transaction times quantiles "0.99,0.95,0.5" } } ### ### ODYSSEY MODULES ### # # You can create your DIY SO file and put here to load on start # Check list of available callback in module.h # # load_module "/tmp/od_modules/audit_module.so" ### ### ADMIN CONSOLE (example) ### #storage "local" { # type "local" #} #database "console" { # user default { # authentication "none" # role "admin" # pool "session" # storage "local" # } #} odyssey-1.6.0-rc1/prometheus/000077500000000000000000000000001526064202200160725ustar00rootroot00000000000000odyssey-1.6.0-rc1/prometheus/exporter/000077500000000000000000000000001526064202200177425ustar00rootroot00000000000000odyssey-1.6.0-rc1/prometheus/exporter/exporter.go000066400000000000000000001122761526064202200221520ustar00rootroot00000000000000// highly inspired by https://github.com/prometheus-community/pgbouncer_exporter/ package main import ( "context" "database/sql" "errors" "fmt" "log/slog" "net/http" "os" "strconv" "strings" "github.com/alecthomas/kingpin/v2" "github.com/lib/pq" "github.com/prometheus/client_golang/prometheus" versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/promslog" "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" "github.com/prometheus/exporter-toolkit/web/kingpinflag" ) const ( namespace = "odyssey" metricsHandlePath = "/metrics" showVersionCommand = "show version;" showVersionExtendedCommand = "show version_extended;" showListsCommand = "show lists;" showInstanceCommand = "show instance;" showIsPausedCommand = "show is_paused;" showErrorsCommand = "show errors;" showStatsCommand = "show stats;" showDatabasesCommand = "show databases;" showPoolsExtendedCommand = "show pools_extended;" poolModeColumnName = "pool_mode" queryQuantilePrefix = "query_" transactionQuantilePrefix = "transaction_" ) var ( versionDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "version", "info"), "The Odyssey version info", []string{"version", "build_type", "compiler", "compiler_version", "arch"}, nil, ) exporterUpDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "exporter", "up"), "The Odyssey exporter status", nil, nil, ) isPausedDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "is_paused"), "The Odyssey paused status", nil, nil, ) avgTxCountDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_tx_per_second"), "Average number of transactions per second reported by Odyssey cron", []string{"database"}, nil, ) avgQueryCountDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_query_per_second"), "Average number of queries per second reported by Odyssey cron", []string{"database"}, nil, ) avgRecvBytesPerSecondDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_recv_bytes_per_second"), "Average bytes per second received from clients (SHOW STATS avg_recv)", []string{"database"}, nil, ) avgSentBytesPerSecondDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_sent_bytes_per_second"), "Average bytes per second sent to servers (SHOW STATS avg_sent)", []string{"database"}, nil, ) avgXactTimeSecondsDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_xact_time_seconds"), "Average transaction time in seconds over the stats window (SHOW STATS avg_xact_time)", []string{"database"}, nil, ) avgQueryTimeSecondsDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_query_time_seconds"), "Average query time in seconds over the stats window (SHOW STATS avg_query_time)", []string{"database"}, nil, ) avgWaitTimeSecondsDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "database", "avg_wait_time_seconds"), "Average wait time for a server in seconds over the stats window (SHOW STATS avg_wait_time)", []string{"database"}, nil, ) clientPoolActiveRouteDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "client_pool", "active_route"), "Active clients currently using the route", []string{"user", "database"}, nil, ) clientPoolWaitingRouteDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "client_pool", "waiting_route"), "Clients connected to the route but not currently using a backend connection (idle between transactions); "+ "see client_pool_queue_route for clients blocked on an exhausted pool", []string{"user", "database"}, nil, ) clientPoolQueueRouteDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "client_pool", "queue_route"), "Clients blocked waiting for a free backend connection (pool exhausted)", []string{"user", "database"}, nil, ) serverPoolCapacityConfiguredRouteDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "server_pool", "capacity_configured_route"), "Configured server pool capacity for a specific route (0 means unlimited)", []string{"user", "database"}, nil, ) clientPoolMaxwaitSecondsRouteDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "client_pool", "maxwait_seconds_route"), "Maximum observed wait time for clients on the route (seconds)", []string{"user", "database"}, nil, ) // Deprecated: we no longer export microseconds variant routePoolModeInfoDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "route", "pool_mode_info"), "Pool mode information for the route", []string{"user", "database", "mode"}, nil, ) routeBytesReceivedTotalDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "route", "bytes_received_total"), "Total bytes received from clients on the route", []string{"user", "database"}, nil, ) routeBytesSentTotalDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "route", "bytes_sent_total"), "Total bytes sent to servers from the route", []string{"user", "database"}, nil, ) routeTCPConnectionsTotalDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "route", "tcp_connections_total"), "Total TCP connections established for the route", []string{"user", "database"}, nil, ) routeQueryDurationSecondsDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "route", "query_duration_seconds"), "Route query duration quantiles", []string{"user", "database", "quantile"}, nil, ) routeTransactionDurationSecondsDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "route", "transaction_duration_seconds"), "Route transaction duration quantiles", []string{"user", "database", "quantile"}, nil, ) errorsTotalDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "errors", "total"), "Total number of Odyssey errors grouped by type", []string{"type"}, nil, ) listMetricNameToDescription = map[string]*(prometheus.Desc){ "databases": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "databases"), "Count of databases", nil, nil), "users": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "users"), "Count of users", nil, nil), "pools": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "pools"), "Count of pools", nil, nil), "free_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "free_clients"), "Count of free clients", nil, nil), "used_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "used_clients"), "Count of used clients", nil, nil), "login_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "login_clients"), "Count of clients in login state", nil, nil), "routing_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "routing_clients"), "Count of clients in routing state (between accept and route assignment)", nil, nil), "free_servers": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "free_servers"), "Count of free servers", nil, nil), "used_servers": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "used_servers"), "Count of used servers", nil, nil), "dns_names": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "cached_dns_names"), "Count of DNS names in the cache", nil, nil), "dns_zones": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "cached_dns_zones"), "Count of DNS zones in the cache", nil, nil), "dns_queries": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "in_flight_dns_queries"), "Count of in-flight DNS queries", nil, nil), "dns_pending": prometheus.NewDesc( prometheus.BuildFQName(namespace, "lists", "pending_dns_queries"), "Count of pending DNS queries", nil, nil), "config_load_failed": prometheus.NewDesc( prometheus.BuildFQName(namespace, "config", "load_failed"), "Whether the last config load failed, leaving the process on its previous configuration", nil, nil), } describeMetricDescs = []*prometheus.Desc{ versionDescription, exporterUpDescription, isPausedDescription, avgTxCountDescription, avgQueryCountDescription, avgRecvBytesPerSecondDescription, avgSentBytesPerSecondDescription, avgXactTimeSecondsDescription, avgQueryTimeSecondsDescription, avgWaitTimeSecondsDescription, clientPoolActiveRouteDescription, clientPoolWaitingRouteDescription, clientPoolQueueRouteDescription, serverPoolCapacityConfiguredRouteDescription, clientPoolMaxwaitSecondsRouteDescription, routePoolModeInfoDescription, routeBytesReceivedTotalDescription, routeBytesSentTotalDescription, routeTCPConnectionsTotalDescription, routeQueryDurationSecondsDescription, routeTransactionDurationSecondsDescription, errorsTotalDescription, serverPoolStateRouteDescription, } ) func writePoolModeInfoMetric(ch chan<- prometheus.Metric, database, user, mode string) { ch <- prometheus.MustNewConstMetric( routePoolModeInfoDescription, prometheus.GaugeValue, 1.0, user, database, mode, ) } func extractFloat(val any, columnName string) (float64, bool, error) { switch v := val.(type) { case nil: return 0, false, nil case int64: return float64(v), true, nil case float64: return v, true, nil case []uint8: parsed, err := strconv.ParseFloat(string(v), 64) if err != nil { return 0, false, fmt.Errorf("can't parse column %q value %q: %w", columnName, string(v), err) } return parsed, true, nil default: return 0, false, fmt.Errorf("got unexpected column %q type %T", columnName, val) } } func extractString(val any, columnName string) (string, error) { switch v := val.(type) { case string: return v, nil case []byte: return string(v), nil case sql.RawBytes: return string(v), nil case fmt.Stringer: return v.String(), nil case nil: return "", fmt.Errorf("column %q is NULL, expected string", columnName) default: return "", fmt.Errorf("expected column %q to be string, got %T", columnName, val) } } type Exporter struct { connector *pq.Connector logger *slog.Logger } type contextCollector struct { exporter *Exporter ctx context.Context } func (c *contextCollector) Describe(ch chan<- *prometheus.Desc) { c.exporter.Describe(ch) } func (c *contextCollector) Collect(ch chan<- prometheus.Metric) { c.exporter.collectWithContext(c.ctx, ch) } type poolColumnMetricDesc struct { desc *prometheus.Desc valueType prometheus.ValueType } var poolsExtendedColumnToMetric = map[string]poolColumnMetricDesc{ "cl_active": { desc: clientPoolActiveRouteDescription, valueType: prometheus.GaugeValue, }, "cl_waiting": { desc: clientPoolWaitingRouteDescription, valueType: prometheus.GaugeValue, }, "cl_queue": { desc: clientPoolQueueRouteDescription, valueType: prometheus.GaugeValue, }, "maxwait": { desc: clientPoolMaxwaitSecondsRouteDescription, valueType: prometheus.GaugeValue, }, // "maxwait_us" is intentionally ignored to avoid duplicate metrics "bytes_received": { desc: routeBytesReceivedTotalDescription, valueType: prometheus.CounterValue, }, "bytes_sent": { desc: routeBytesSentTotalDescription, valueType: prometheus.CounterValue, }, "tcp_conn_count": { desc: routeTCPConnectionsTotalDescription, valueType: prometheus.CounterValue, }, } // unified state metric for server pool var serverPoolStateRouteDescription = prometheus.NewDesc( prometheus.BuildFQName(namespace, "server_pool", "state_route"), "Server pool state per route", []string{"user", "database", "state"}, nil, ) // routeKey identifies a route by backend database and user type routeKey struct { database string user string } type rowIterator interface { Columns() ([]string, error) Next() bool Scan(dest ...any) error Err() error } func NewExporter(connectionString string, logger *slog.Logger) (*Exporter, error) { connector, err := pq.NewConnector(connectionString) if err != nil { return nil, err } return &Exporter{ connector: connector, logger: logger, }, nil } func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { for _, desc := range describeMetricDescs { ch <- desc } for _, desc := range listMetricNameToDescription { ch <- desc } } func (exporter *Exporter) getDB() (*sql.DB, error) { db := sql.OpenDB(exporter.connector) if db == nil { return nil, errors.New("error opening DB") } db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) return db, nil } func (exporter *Exporter) Collect(ch chan<- prometheus.Metric) { exporter.collectWithContext(context.Background(), ch) } func (exporter *Exporter) collectWithContext(ctx context.Context, ch chan<- prometheus.Metric) { logger := exporter.logger var up = 1.0 defer func() { ch <- prometheus.MustNewConstMetric(exporterUpDescription, prometheus.GaugeValue, up) }() db, err := exporter.getDB() if err != nil { logger.Warn("can't connect to Odyssey", "err", err.Error()) up = 0 return } defer db.Close() if err := exporter.collectWithDB(ctx, ch, db); err != nil { logger.Error("scrape failed", "err", err) up = 0 } } func (exporter *Exporter) collectWithDB(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { logger := exporter.logger var scrapeErr error runStep := func(name string, fn func() error) { if fnErr := fn(); fnErr != nil { logger.Error("scrape step failed", "step", name, "err", fnErr) scrapeErr = errors.Join(scrapeErr, fmt.Errorf("%s: %w", name, fnErr)) } } runStep("version", func() error { return exporter.sendVersionMetric(ctx, ch, db) }) runStep("lists", func() error { return exporter.sendListsMetrics(ctx, ch, db) }) runStep("instance", func() error { return exporter.sendInstanceMetrics(ctx, ch, db) }) runStep("is_paused", func() error { return exporter.sendIsPausedMetric(ctx, ch, db) }) runStep("errors", func() error { return exporter.sendErrorMetrics(ctx, ch, db) }) runStep("stats", func() error { return exporter.sendStatsMetrics(ctx, ch, db) }) var poolCapacities map[routeKey]float64 runStep("databases", func() error { var err error poolCapacities, err = exporter.collectRoutePoolCapacities(ctx, db) return err }) if poolCapacities == nil { poolCapacities = map[routeKey]float64{} } runStep("pools_extended", func() error { return exporter.sendPoolsExtendedMetrics(ctx, ch, db, poolCapacities) }) if scrapeErr != nil { return scrapeErr } return nil } func (exporter *Exporter) collectRoutePoolCapacities(ctx context.Context, db *sql.DB) (map[routeKey]float64, error) { rows, err := db.QueryContext(ctx, showDatabasesCommand) if err != nil { return nil, fmt.Errorf("error getting databases: %w", err) } defer rows.Close() columns, err := rows.Columns() if err != nil { return nil, fmt.Errorf("can't get columns of databases: %w", err) } nameIdx := -1 dbIdx := -1 userIdx := -1 poolSizeIdx := -1 for idx, name := range columns { switch name { case "name": nameIdx = idx case "database": dbIdx = idx case "force_user": userIdx = idx case "pool_size": poolSizeIdx = idx } } // require pool_size and route name column if poolSizeIdx == -1 || nameIdx == -1 { return nil, fmt.Errorf("unexpected databases output format") } result := make(map[routeKey]float64) rawColumns := make([]sql.RawBytes, len(columns)) dest := make([]any, len(columns)) for i := range dest { dest[i] = &rawColumns[i] } for rows.Next() { for i := range rawColumns { rawColumns[i] = nil } if err := rows.Scan(dest...); err != nil { return nil, fmt.Errorf("error scanning databases row: %w", err) } routeName := "" if rawColumns[nameIdx] != nil { routeName = string(rawColumns[nameIdx]) } else if dbIdx != -1 && rawColumns[dbIdx] != nil { // defensive fallback, should not happen routeName = string(rawColumns[dbIdx]) } backendUser := "" if userIdx != -1 && rawColumns[userIdx] != nil { backendUser = string(rawColumns[userIdx]) } poolSizeValue := 0.0 if poolSizeIdx != -1 && rawColumns[poolSizeIdx] != nil { poolSizeStr := string(rawColumns[poolSizeIdx]) if poolSizeStr != "" { poolSizeValue, err = strconv.ParseFloat(poolSizeStr, 64) if err != nil { return nil, fmt.Errorf("can't parse pool_size for %s: %w", routeName, err) } } } // Store capacity for exact route+user (by route alias) result[routeKey{database: routeName, user: backendUser}] = poolSizeValue // Also store a route-wide fallback under empty user, prefer the maximum if multiple users exist if existing, ok := result[routeKey{database: routeName, user: ""}]; !ok || poolSizeValue > existing { result[routeKey{database: routeName, user: ""}] = poolSizeValue } // Additionally index by backend database name (SHOW DATABASES "database" column) to handle routes // that may not expose alias consistently across commands in some setups if dbIdx != -1 && rawColumns[dbIdx] != nil { backendDB := string(rawColumns[dbIdx]) result[routeKey{database: backendDB, user: backendUser}] = poolSizeValue if existing, ok := result[routeKey{database: backendDB, user: ""}]; !ok || poolSizeValue > existing { result[routeKey{database: backendDB, user: ""}] = poolSizeValue } } } if err := rows.Err(); err != nil { return nil, fmt.Errorf("error iterating databases rows: %w", err) } return result, nil } func (exporter *Exporter) sendVersionMetric(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { // Try version_extended first for full build info, fall back to plain version // for backward compatibility with older Odyssey instances. rows, err := db.QueryContext(ctx, showVersionExtendedCommand) if err != nil { return exporter.sendVersionMetricFallback(ctx, ch, db, err) } defer rows.Close() var columnNames []string columnNames, err = rows.Columns() if err != nil { return fmt.Errorf("can't get columns for version: %w", err) } if !rows.Next() { return fmt.Errorf("empty version_extended command output") } if len(columnNames) == 5 { var version, buildType, compiler, compilerVersion, arch string err = rows.Scan(&version, &buildType, &compiler, &compilerVersion, &arch) if err != nil { return fmt.Errorf("can't scan version_extended columns: %w", err) } ch <- prometheus.MustNewConstMetric( versionDescription, prometheus.GaugeValue, 1.0, version, buildType, compiler, compilerVersion, arch, ) return nil } // Unexpected column count — try fallback rows.Close() return exporter.sendVersionMetricFallback(ctx, ch, db, nil) } func (exporter *Exporter) sendVersionMetricFallback(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB, prevErr error) error { rows, err := db.QueryContext(ctx, showVersionCommand) if err != nil { if prevErr != nil { return fmt.Errorf("error getting version (extended: %v, fallback: %w)", prevErr, err) } return fmt.Errorf("error getting version: %w", err) } defer rows.Close() if !rows.Next() { return fmt.Errorf("empty version command output") } var version string err = rows.Scan(&version) if err != nil { return fmt.Errorf("can't scan version column: %w", err) } ch <- prometheus.MustNewConstMetric( versionDescription, prometheus.GaugeValue, 1.0, version, "", "", "", "", ) return nil } func (exporter *Exporter) sendIsPausedMetric(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { rows, err := db.QueryContext(ctx, showIsPausedCommand) if err != nil { return fmt.Errorf("error getting is_paused: %w", err) } defer rows.Close() var columnNames []string columnNames, err = rows.Columns() if err != nil { return fmt.Errorf("can't get columns for paused status: %w", err) } if len(columnNames) != 1 || columnNames[0] != "is_paused" { return fmt.Errorf("unexpected paused command output format") } var isPaused bool if !rows.Next() { return fmt.Errorf("empty paused command output") } err = rows.Scan(&isPaused) if err != nil { return fmt.Errorf("can't scan paused column: %w", err) } value := 1.0 if !isPaused { value = 0.0 } ch <- prometheus.MustNewConstMetric( isPausedDescription, prometheus.GaugeValue, value, ) return nil } // listMetricDescription returns the description to export a ", " // row under. Names present in listMetricNameToDescription keep their curated // help text and metric name; anything else is exported under a generated // description so that a server which reports a new counter is picked up // without an exporter release. func listMetricDescription(view, name string) (*prometheus.Desc, bool) { if description, ok := listMetricNameToDescription[name]; ok { return description, true } // Guard against a server sending something that cannot be a metric name. if !isValidMetricSuffix(name) { return nil, false } return prometheus.NewDesc( prometheus.BuildFQName(namespace, view, name), fmt.Sprintf("Value of %q as reported by SHOW %s", name, strings.ToUpper(view)), nil, nil), true } // isValidMetricSuffix reports whether name can be used as the last element of // a Prometheus metric name: [a-zA-Z_][a-zA-Z0-9_]*. func isValidMetricSuffix(name string) bool { if name == "" { return false } for i, c := range name { switch { case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c == '_': case i > 0 && c >= '0' && c <= '9': default: return false } } return true } func (exporter *Exporter) sendListsMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { return exporter.sendKeyValueMetrics(ctx, ch, db, showListsCommand, "lists") } // sendInstanceMetrics scrapes SHOW INSTANCE, which reports the state of the // Odyssey process itself. Older servers do not implement it, so a query error // is not treated as a scrape failure. func (exporter *Exporter) sendInstanceMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { err := exporter.sendKeyValueMetrics(ctx, ch, db, showInstanceCommand, "instance") if err != nil { exporter.logger.Debug("skipping instance metrics", "err", err) } return nil } // sendKeyValueMetrics scrapes a console view shaped as ", " rows // and exports every row it can turn into a metric. func (exporter *Exporter) sendKeyValueMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB, command, view string) error { rows, err := db.QueryContext(ctx, command) if err != nil { return fmt.Errorf("error getting %s: %w", view, err) } defer rows.Close() columns, err := rows.Columns() if err != nil { return fmt.Errorf("can't get columns of %s", view) } if len(columns) != 2 || columns[0] != "list" || columns[1] != "items" { return fmt.Errorf("invalid format of %s output", view) } // A row naming an already-exported metric would make the registry reject // the whole scrape, so keep only the first occurrence of each name. seen := make(map[string]struct{}, len(columns)) var list string var items sql.RawBytes for rows.Next() { if err = rows.Scan(&list, &items); err != nil { return fmt.Errorf("error scanning %s row: %w", view, err) } value, err := strconv.ParseFloat(string(items), 64) if err != nil { // A future server may report a non-numeric field here. Skip it // rather than failing the scrape for every other metric. exporter.logger.Debug("skipping non-numeric field", "view", view, "field", list, "value", string(items)) continue } description, ok := listMetricDescription(view, list) if !ok { exporter.logger.Debug("skipping unusable field name", "view", view, "field", list) continue } if _, duplicate := seen[list]; duplicate { exporter.logger.Debug("skipping duplicate field", "view", view, "field", list) continue } seen[list] = struct{}{} ch <- prometheus.MustNewConstMetric(description, prometheus.GaugeValue, value) } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating %s rows: %w", view, err) } return nil } func (exporter *Exporter) sendErrorMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { rows, err := db.QueryContext(ctx, showErrorsCommand) if err != nil { return fmt.Errorf("error getting errors: %w", err) } defer rows.Close() columns, err := rows.Columns() if err != nil { return fmt.Errorf("can't get columns of errors") } if len(columns) != 2 || columns[0] != "error_type" || columns[1] != "count" { return fmt.Errorf("invalid format of errors output") } var errorType string var count sql.RawBytes for rows.Next() { if err = rows.Scan(&errorType, &count); err != nil { return fmt.Errorf("error scanning lists row: %w", err) } value, err := strconv.ParseFloat(string(count), 64) if err != nil { return fmt.Errorf("can't parse count of %q: %w", string(count), err) } ch <- prometheus.MustNewConstMetric( errorsTotalDescription, prometheus.CounterValue, value, errorType, ) } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating errors rows: %w", err) } return nil } func (exporter *Exporter) sendStatsMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { rows, err := db.QueryContext(ctx, showStatsCommand) if err != nil { return fmt.Errorf("error getting stats: %w", err) } defer rows.Close() columns, err := rows.Columns() if err != nil { return fmt.Errorf("can't get columns of stats") } if len(columns) == 0 { return fmt.Errorf("stats output has no columns") } databaseIdx := -1 avgXactIdx := -1 avgQueryIdx := -1 avgRecvIdx := -1 avgSentIdx := -1 avgXactTimeIdx := -1 avgQueryTimeIdx := -1 avgWaitTimeIdx := -1 for idx, name := range columns { switch name { case "database": databaseIdx = idx case "avg_xact_count": avgXactIdx = idx case "avg_query_count": avgQueryIdx = idx case "avg_recv": avgRecvIdx = idx case "avg_sent": avgSentIdx = idx case "avg_xact_time": avgXactTimeIdx = idx case "avg_query_time": avgQueryTimeIdx = idx case "avg_wait_time": avgWaitTimeIdx = idx } } if databaseIdx == -1 || avgXactIdx == -1 || avgQueryIdx == -1 { return fmt.Errorf("unexpected stats columns, database=%d avg_xact_count=%d avg_query_count=%d", databaseIdx, avgXactIdx, avgQueryIdx) } rawColumns := make([]sql.RawBytes, len(columns)) dest := make([]any, len(columns)) for i := range dest { dest[i] = &rawColumns[i] } for rows.Next() { for i := range rawColumns { rawColumns[i] = nil } if err = rows.Scan(dest...); err != nil { return fmt.Errorf("error scanning stats row: %w", err) } if rawColumns[databaseIdx] == nil { continue } database := string(rawColumns[databaseIdx]) if database == "" { continue } avgTxValue := 0.0 if rawColumns[avgXactIdx] != nil { avgTxValue, err = strconv.ParseFloat(string(rawColumns[avgXactIdx]), 64) if err != nil { return fmt.Errorf("can't parse avg_xact_count for %s: %w", database, err) } } avgQueryValue := 0.0 if rawColumns[avgQueryIdx] != nil { avgQueryValue, err = strconv.ParseFloat(string(rawColumns[avgQueryIdx]), 64) if err != nil { return fmt.Errorf("can't parse avg_query_count for %s: %w", database, err) } } avgRecvBps := 0.0 if avgRecvIdx != -1 && rawColumns[avgRecvIdx] != nil { avgRecvBps, err = strconv.ParseFloat(string(rawColumns[avgRecvIdx]), 64) if err != nil { return fmt.Errorf("can't parse avg_recv for %s: %w", database, err) } } avgSentBps := 0.0 if avgSentIdx != -1 && rawColumns[avgSentIdx] != nil { avgSentBps, err = strconv.ParseFloat(string(rawColumns[avgSentIdx]), 64) if err != nil { return fmt.Errorf("can't parse avg_sent for %s: %w", database, err) } } // SHOW STATS times are in microseconds; convert to seconds for *_seconds metrics avgXactTimeSec := 0.0 if avgXactTimeIdx != -1 && rawColumns[avgXactTimeIdx] != nil { v, convErr := strconv.ParseFloat(string(rawColumns[avgXactTimeIdx]), 64) if convErr != nil { return fmt.Errorf("can't parse avg_xact_time for %s: %w", database, convErr) } avgXactTimeSec = v / 1e6 } avgQueryTimeSec := 0.0 if avgQueryTimeIdx != -1 && rawColumns[avgQueryTimeIdx] != nil { v, convErr := strconv.ParseFloat(string(rawColumns[avgQueryTimeIdx]), 64) if convErr != nil { return fmt.Errorf("can't parse avg_query_time for %s: %w", database, convErr) } avgQueryTimeSec = v / 1e6 } avgWaitTimeSec := 0.0 if avgWaitTimeIdx != -1 && rawColumns[avgWaitTimeIdx] != nil { v, convErr := strconv.ParseFloat(string(rawColumns[avgWaitTimeIdx]), 64) if convErr != nil { return fmt.Errorf("can't parse avg_wait_time for %s: %w", database, convErr) } avgWaitTimeSec = v / 1e6 } ch <- prometheus.MustNewConstMetric( avgTxCountDescription, prometheus.GaugeValue, avgTxValue, database, ) ch <- prometheus.MustNewConstMetric( avgQueryCountDescription, prometheus.GaugeValue, avgQueryValue, database, ) if avgRecvIdx != -1 { ch <- prometheus.MustNewConstMetric( avgRecvBytesPerSecondDescription, prometheus.GaugeValue, avgRecvBps, database, ) } if avgSentIdx != -1 { ch <- prometheus.MustNewConstMetric( avgSentBytesPerSecondDescription, prometheus.GaugeValue, avgSentBps, database, ) } if avgXactTimeIdx != -1 { ch <- prometheus.MustNewConstMetric( avgXactTimeSecondsDescription, prometheus.GaugeValue, avgXactTimeSec, database, ) } if avgQueryTimeIdx != -1 { ch <- prometheus.MustNewConstMetric( avgQueryTimeSecondsDescription, prometheus.GaugeValue, avgQueryTimeSec, database, ) } if avgWaitTimeIdx != -1 { ch <- prometheus.MustNewConstMetric( avgWaitTimeSecondsDescription, prometheus.GaugeValue, avgWaitTimeSec, database, ) } } return rows.Err() } func (exporter *Exporter) sendPoolsExtendedMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB, capacities map[routeKey]float64) error { rows, err := db.QueryContext(ctx, showPoolsExtendedCommand) if err != nil { return fmt.Errorf("error getting pools: %w", err) } defer rows.Close() return exporter.processPoolsExtendedRows(rows, capacities, ch) } func (exporter *Exporter) processPoolsExtendedRows(rows rowIterator, capacities map[routeKey]float64, ch chan<- prometheus.Metric) error { columns, err := rows.Columns() if err != nil { return fmt.Errorf("can't get columns of pools") } if len(columns) <= 2 || columns[0] != "database" || columns[1] != "user" { return fmt.Errorf("invalid format of pools output") } values := make([]any, len(columns)) scanTargets := make([]any, len(columns)) for i := range scanTargets { scanTargets[i] = &values[i] } for rows.Next() { for i := range values { values[i] = nil } if err := rows.Scan(scanTargets...); err != nil { return fmt.Errorf("error scanning pool extended row: %w", err) } if err := exporter.processPoolRow(columns, values, capacities, ch); err != nil { return err } } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating pools rows: %w", err) } return nil } func (exporter *Exporter) processPoolRow(columns []string, values []any, capacities map[routeKey]float64, ch chan<- prometheus.Metric) error { database, err := extractString(values[0], columns[0]) if err != nil { return err } user, err := extractString(values[1], columns[1]) if err != nil { return err } if database == "aggregated" && user == "aggregated" { return nil } serverActive := 0.0 serverIdle := 0.0 serverUsed := 0.0 serverTested := 0.0 serverLogin := 0.0 for i := 2; i < len(columns); i++ { columnName := columns[i] val := values[i] if columnName == poolModeColumnName { if val == nil { continue } mode, err := extractString(val, poolModeColumnName) if err != nil { return err } writePoolModeInfoMetric(ch, database, user, mode) continue } if strings.HasPrefix(columnName, queryQuantilePrefix) { value, _, err := extractFloat(val, columnName) if err != nil { return err } value = value / 1e6 quantile := strings.TrimPrefix(columnName, queryQuantilePrefix) ch <- prometheus.MustNewConstMetric( routeQueryDurationSecondsDescription, prometheus.GaugeValue, value, user, database, quantile, ) continue } if strings.HasPrefix(columnName, transactionQuantilePrefix) { value, _, err := extractFloat(val, columnName) if err != nil { return err } value = value / 1e6 quantile := strings.TrimPrefix(columnName, transactionQuantilePrefix) ch <- prometheus.MustNewConstMetric( routeTransactionDurationSecondsDescription, prometheus.GaugeValue, value, user, database, quantile, ) continue } value, _, err := extractFloat(val, columnName) if err != nil { return err } switch columnName { case "sv_active": serverActive = value continue case "sv_idle": serverIdle = value continue case "sv_used": serverUsed = value continue case "sv_tested": serverTested = value continue case "sv_login": serverLogin = value continue } // Ignore deprecated microseconds column silently if columnName == "maxwait_us" { continue } if metricDesc, ok := poolsExtendedColumnToMetric[columnName]; ok { ch <- prometheus.MustNewConstMetric( metricDesc.desc, metricDesc.valueType, value, user, database, ) continue } // Skip columns from a newer Odyssey rather than failing the whole // scrape, so the two can be upgraded independently if exporter.logger != nil { exporter.logger.Debug("ignoring unknown pools_extended column", "column", columnName) } } // Always export configured capacity (0 means unlimited) based on SHOW DATABASES // Prefer exact database+user match; fall back to database-only if present var configuredCapacity float64 if v, ok := capacities[routeKey{database: database, user: user}]; ok { configuredCapacity = v } else if v, ok := capacities[routeKey{database: database, user: ""}]; ok { configuredCapacity = v } // If SHOW DATABASES did not yield a capacity for this route (or reported 0/unlimited), // fall back to the observed current server slots (sv_active + sv_idle) as a best-effort proxy. if configuredCapacity <= 0 { configuredCapacity = serverActive + serverIdle } ch <- prometheus.MustNewConstMetric( serverPoolCapacityConfiguredRouteDescription, prometheus.GaugeValue, configuredCapacity, user, database, ) // Unified state metric family ch <- prometheus.MustNewConstMetric(serverPoolStateRouteDescription, prometheus.GaugeValue, serverActive, user, database, "active") ch <- prometheus.MustNewConstMetric(serverPoolStateRouteDescription, prometheus.GaugeValue, serverIdle, user, database, "idle") ch <- prometheus.MustNewConstMetric(serverPoolStateRouteDescription, prometheus.GaugeValue, serverUsed, user, database, "used") ch <- prometheus.MustNewConstMetric(serverPoolStateRouteDescription, prometheus.GaugeValue, serverTested, user, database, "tested") ch <- prometheus.MustNewConstMetric(serverPoolStateRouteDescription, prometheus.GaugeValue, serverLogin, user, database, "login") return nil } func main() { connectionStringPtr := kingpin.Flag("odyssey.connectionString", "Connection string for accessing Odyssey.").Default("host=localhost port=6432 user=console dbname=console sslmode=disable").String() scrapeTimeoutPtr := kingpin.Flag("odyssey.scrape-timeout", "Maximum duration allowed for a single scrape (e.g. 5s, 30s).").Default("5s").Duration() toolkitFlags := kingpinflag.AddFlags(kingpin.CommandLine, ":9876") kingpin.Version(version.Print("odyssey_exporter")) kingpin.HelpFlag.Short('h') kingpin.Parse() promslogConfig := &promslog.Config{} logger := promslog.New(promslogConfig) logger.Info("Starting odyssey_exporter", "version", version.Info()) exporter, err := NewExporter(*connectionStringPtr, logger) if err != nil { logger.Error("Error creating exporter", "err", err) os.Exit(1) } prometheus.MustRegister(versioncollector.NewCollector("odyssey_exporter")) handlerOpts := promhttp.HandlerOpts{ ErrorHandling: promhttp.ContinueOnError, } http.HandleFunc(metricsHandlePath, func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if timeout := *scrapeTimeoutPtr; timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel() } perScrapeRegistry := prometheus.NewRegistry() perScrapeRegistry.MustRegister(&contextCollector{ exporter: exporter, ctx: ctx, }) gatherers := prometheus.Gatherers{ perScrapeRegistry, prometheus.DefaultGatherer, } promhttp.HandlerFor(gatherers, handlerOpts).ServeHTTP(w, r) }) landingConfig := web.LandingConfig{ Name: "Odyssey Exporter", Description: "Prometheus Exporter for Odyssey instances", Version: version.Info(), Links: []web.LandingLinks{ { Address: metricsHandlePath, Text: "Metrics", }, }, } landingPage, err := web.NewLandingPage(landingConfig) if err != nil { logger.Error("Error creating landing page", "err", err) os.Exit(1) } http.Handle("/", landingPage) srv := &http.Server{} if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil { logger.Error("Error starting server", "err", err) os.Exit(1) } } odyssey-1.6.0-rc1/prometheus/exporter/exporter_bench_test.go000066400000000000000000000072221526064202200243420ustar00rootroot00000000000000package main import ( "fmt" "testing" ) var benchmarkSinkResult poolBenchmarkSink type poolBenchmarkSink struct { counter float64 } func BenchmarkPoolsExtended(b *testing.B) { columns, data, capacities := buildBenchmarkPoolData(400) b.Run("optimized", func(b *testing.B) { sink := poolBenchmarkSink{} for i := 0; i < b.N; i++ { if err := runOptimized(columns, data, capacities, &sink); err != nil { b.Fatal(err) } } benchmarkSinkResult = sink }) b.Run("legacy", func(b *testing.B) { sink := poolBenchmarkSink{} for i := 0; i < b.N; i++ { if err := runLegacy(columns, data, capacities, &sink); err != nil { b.Fatal(err) } } benchmarkSinkResult = sink }) } func runOptimized(columns []string, data [][]any, capacities map[routeKey]float64, sink *poolBenchmarkSink) error { values := make([]any, len(columns)) for _, row := range data { for i := range values { values[i] = row[i] } if err := consumePoolRow(columns, values, capacities, sink); err != nil { return err } } return nil } func runLegacy(columns []string, data [][]any, capacities map[routeKey]float64, sink *poolBenchmarkSink) error { for _, row := range data { vals := make([]any, len(columns)) for i := range row { cell := new(any) *cell = row[i] vals[i] = cell } extracted := make([]any, len(columns)) for i := range vals { extracted[i] = *(vals[i].(*any)) } if err := consumePoolRow(columns, extracted, capacities, sink); err != nil { return err } } return nil } func consumePoolRow(columns []string, values []any, capacities map[routeKey]float64, sink *poolBenchmarkSink) error { database, err := extractString(values[0], columns[0]) if err != nil { return err } user, err := extractString(values[1], columns[1]) if err != nil { return err } if database == "aggregated" && user == "aggregated" { return nil } serverActive := 0.0 serverIdle := 0.0 serverUsed := 0.0 serverTested := 0.0 serverLogin := 0.0 for i := 2; i < len(columns); i++ { columnName := columns[i] val := values[i] if columnName == poolModeColumnName { continue } value, _, err := extractFloat(val, columnName) if err != nil { continue } switch columnName { case "sv_active": serverActive = value case "sv_idle": serverIdle = value case "sv_used": serverUsed = value case "sv_tested": serverTested = value case "sv_login": serverLogin = value } } var configuredCapacity float64 if v, ok := capacities[routeKey{database: database, user: user}]; ok { configuredCapacity = v } sink.counter += serverActive + serverIdle + serverUsed + serverTested + serverLogin + configuredCapacity return nil } func buildBenchmarkPoolData(routes int) ([]string, [][]any, map[routeKey]float64) { columns := []string{ "database", "user", "cl_active", "cl_waiting", "sv_active", "sv_idle", "sv_used", "sv_tested", "sv_login", "maxwait", "bytes_received", "bytes_sent", "tcp_conn_count", poolModeColumnName, } rows := make([][]any, routes) capacities := make(map[routeKey]float64, routes) for i := range routes { row := make([]any, len(columns)) dbName := fmt.Sprintf("db_%03d", i) user := fmt.Sprintf("user_%03d", i) row[0] = dbName row[1] = user row[2] = float64(i%10 + 1) row[3] = float64(i % 5) row[4] = float64(i%8 + 4) row[5] = float64(i % 7) row[6] = float64(i % 3) row[7] = float64(i % 2) row[8] = float64(i % 4) row[9] = float64(i % 6) row[10] = float64(i * 1024) row[11] = float64(i * 2048) row[12] = float64(i % 100) row[13] = "session" rows[i] = row capacities[routeKey{database: dbName, user: user}] = 32 } return columns, rows, capacities } odyssey-1.6.0-rc1/prometheus/exporter/exporter_test.go000066400000000000000000000177741526064202200232200ustar00rootroot00000000000000package main import ( "context" "errors" "io" "log/slog" "regexp" "strings" "testing" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) func TestExporterDescribeDoesNotTouchDatabase(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, // connector intentionally left nil — Describe must not dereference it. } expected := len(describeMetricDescs) + len(listMetricNameToDescription) descCh := make(chan *prometheus.Desc, expected) defer func() { if r := recover(); r != nil { t.Fatalf("Describe should not interact with the database or panic, got %v", r) } }() exporter.Describe(descCh) close(descCh) var got []*prometheus.Desc for desc := range descCh { if desc == nil { t.Fatal("Describe emitted nil descriptor") } got = append(got, desc) } if len(got) != expected { t.Fatalf("unexpected descriptor count, got %d, want %d", len(got), expected) } } func TestSendPoolsExtendedMetricsHandlesByteColumns(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, } db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() rows := sqlmock. NewRows([]string{"database", "user", "cl_active", "sv_active", "sv_idle"}). AddRow([]byte("db1"), []byte("user1"), 1, 2, 3) mock.ExpectQuery(regexp.QuoteMeta(showPoolsExtendedCommand)).WillReturnRows(rows) ch := make(chan prometheus.Metric, 32) err = exporter.sendPoolsExtendedMetrics(context.Background(), ch, db, map[routeKey]float64{}) if err != nil { t.Fatalf("sendPoolsExtendedMetrics returned error: %v", err) } close(ch) count := 0 for range ch { count++ } if count == 0 { t.Fatalf("expected at least one metric, got %d", count) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("unmet expectations: %v", err) } } func TestSendPoolsExtendedMetricsExportsQueueColumn(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, } db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() rows := sqlmock. NewRows([]string{"database", "user", "cl_active", "cl_waiting", "cl_queue"}). AddRow("db1", "user1", 1, 2, 7) mock.ExpectQuery(regexp.QuoteMeta(showPoolsExtendedCommand)).WillReturnRows(rows) ch := make(chan prometheus.Metric, 32) err = exporter.sendPoolsExtendedMetrics(context.Background(), ch, db, map[routeKey]float64{}) if err != nil { t.Fatalf("sendPoolsExtendedMetrics returned error: %v", err) } close(ch) found := false for metric := range ch { if metric.Desc().String() != clientPoolQueueRouteDescription.String() { continue } found = true var written dto.Metric if err := metric.Write(&written); err != nil { t.Fatalf("failed to write metric: %v", err) } if got := written.GetGauge().GetValue(); got != 7 { t.Fatalf("unexpected cl_queue value, got %v, want 7", got) } } if !found { t.Fatalf("expected %s metric to be exported", clientPoolQueueRouteDescription) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("unmet expectations: %v", err) } } func TestSendPoolsExtendedMetricsSkipsUnknownColumns(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, } db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() rows := sqlmock. NewRows([]string{"database", "user", "cl_active", "cl_from_the_future"}). AddRow("db1", "user1", 5, 42) mock.ExpectQuery(regexp.QuoteMeta(showPoolsExtendedCommand)).WillReturnRows(rows) ch := make(chan prometheus.Metric, 32) err = exporter.sendPoolsExtendedMetrics(context.Background(), ch, db, map[routeKey]float64{}) if err != nil { t.Fatalf("unknown column must not fail the scrape, got: %v", err) } close(ch) found := false for metric := range ch { if metric.Desc().String() == clientPoolActiveRouteDescription.String() { found = true } } if !found { t.Fatalf("expected known columns to still be exported alongside an unknown one") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("unmet expectations: %v", err) } } func TestCollectWithDBContinuesAfterStepFailure(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, } db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() mock.ExpectQuery(regexp.QuoteMeta(showVersionExtendedCommand)). WillReturnRows(sqlmock.NewRows([]string{"version", "build_type", "compiler", "compiler_version", "arch"}). AddRow("v1.0", "release", "gcc", "12.0", "x86_64")) mock.ExpectQuery(regexp.QuoteMeta(showListsCommand)). WillReturnError(errors.New("boom lists")) mock.ExpectQuery(regexp.QuoteMeta(showIsPausedCommand)). WillReturnRows(sqlmock.NewRows([]string{"is_paused"}).AddRow(false)) mock.ExpectQuery(regexp.QuoteMeta(showErrorsCommand)). WillReturnRows(sqlmock.NewRows([]string{"error_type", "count"})) mock.ExpectQuery(regexp.QuoteMeta(showStatsCommand)). WillReturnRows(sqlmock.NewRows([]string{"database", "avg_xact_count", "avg_query_count"}). AddRow("db1", 1, 2)) mock.ExpectQuery(regexp.QuoteMeta(showDatabasesCommand)). WillReturnRows(sqlmock.NewRows([]string{"name", "pool_size"}).AddRow("db1", "4")) mock.ExpectQuery(regexp.QuoteMeta(showPoolsExtendedCommand)). WillReturnRows(sqlmock.NewRows([]string{"database", "user", "cl_active"}).AddRow("db1", "user1", 1)) ch := make(chan prometheus.Metric, 64) err = exporter.collectWithDB(context.Background(), ch, db) if err == nil { t.Fatalf("expected collectWithDB to return error when a step fails") } close(ch) foundIsPaused := false for metric := range ch { if metric.Desc().String() == isPausedDescription.String() { foundIsPaused = true } } if !foundIsPaused { t.Fatalf("expected is_paused metric even when earlier step failed") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("unmet expectations: %v", err) } } func TestSendListsMetricsReturnsRowsErr(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, } db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() rows := sqlmock.NewRows([]string{"list", "items"}). AddRow("databases", "1"). AddRow("users", "2"). RowError(1, errors.New("cursor error")) mock.ExpectQuery(regexp.QuoteMeta(showListsCommand)).WillReturnRows(rows) metricCh := make(chan prometheus.Metric, len(listMetricNameToDescription)) err = exporter.sendListsMetrics(context.Background(), metricCh, db) if err == nil || !strings.Contains(err.Error(), "cursor error") { t.Fatalf("expected cursor error, got %v", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("unmet expectations: %v", err) } } func TestCollectWithDBRespectsContextCancellation(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) exporter := &Exporter{ logger: logger, } db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() for _, query := range []string{ showVersionExtendedCommand, showListsCommand, showIsPausedCommand, showErrorsCommand, showStatsCommand, showDatabasesCommand, showPoolsExtendedCommand, } { mock.ExpectQuery(regexp.QuoteMeta(query)). WillReturnError(context.DeadlineExceeded) } err = exporter.collectWithDB(context.Background(), make(chan prometheus.Metric, 1), db) if err == nil { t.Fatalf("expected error when database returns deadline exceeded") } if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected context.DeadlineExceeded error, got %v", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("unmet expectations: %v", err) } } odyssey-1.6.0-rc1/prometheus/exporter/go.mod000066400000000000000000000026551526064202200210600ustar00rootroot00000000000000module github.com/odyssey/prometheus/odyssey_exporter go 1.25.0 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/lib/pq v1.12.3 github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.3 github.com/prometheus/common v0.71.0 github.com/prometheus/exporter-toolkit v0.20.0 ) require ( github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/mdlayher/socket v0.6.0 // indirect github.com/mdlayher/vsock v1.3.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/prometheus/procfs v0.21.1 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.12 // indirect ) odyssey-1.6.0-rc1/prometheus/exporter/go.sum000066400000000000000000000165441526064202200211070ustar00rootroot00000000000000github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU= github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18= github.com/mdlayher/vsock v1.3.0 h1:bqQfZ1OznI03y6YiXp2sze05RVdzLn/zsfjnjd4+ivI= github.com/mdlayher/vsock v1.3.0/go.mod h1:WsuksavOvwCnV5UqGHUkvAvCy+Dqy81y4goKQTzxxNY= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= github.com/prometheus/exporter-toolkit v0.20.0 h1:hz3g2aPcq3mXlQSt1MGjj2rwVk1wtRalF+/FjYxFRkI= github.com/prometheus/exporter-toolkit v0.20.0/go.mod h1:gIIY0Mw0ci1wgYscdeMqVh6FUPYJca549eOkE39nU64= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= odyssey-1.6.0-rc1/scripts/000077500000000000000000000000001526064202200153665ustar00rootroot00000000000000odyssey-1.6.0-rc1/scripts/centos/000077500000000000000000000000001526064202200166615ustar00rootroot00000000000000odyssey-1.6.0-rc1/scripts/centos/odyssey.spec000066400000000000000000000023631526064202200212400ustar00rootroot00000000000000Name: odyssey Version: 1.0 Release: 1%{?dist} Summary: Advanced multi-threaded PostgreSQL connection pooler and request router Group: Applications/Network License: BSD 3-clause URL: https://github.com/yandex/odyssey Source0: odyssey.tar.gz BuildRequires: cmake BuildRequires: openssl-devel BuildRequires: zlib-devel BuildRequires: gcc BuildRequires: flex BuildRequires: bison %description Advanced multi-threaded PostgreSQL connection pooler and request router %prep %setup -q -n odyssey %build cmake . make %{?_smp_mflags} %install install -D -m 755 sources/odyssey $RPM_BUILD_ROOT/usr/bin/odyssey install -D -m 644 odyssey.conf $RPM_BUILD_ROOT/etc/odyssey/odyssey.conf install -D -m 644 scripts/systemd/odyssey.service $RPM_BUILD_ROOT/usr/lib/systemd/system/odyssey.service install -D -m 644 scripts/systemd/odyssey@.service $RPM_BUILD_ROOT/usr/lib/systemd/system/odyssey@.service %pre useradd -md /usr/lib/odyssey odyssey >/dev/null 2>&1 || exit 0 %files %attr(0755, root, root) /usr/bin/odyssey %dir %attr(0755, root, root) /etc/odyssey/ %config(noreplace) %attr(0644, root, root) /etc/odyssey/odyssey.conf %attr(0644, root, root) /usr/lib/systemd/system/odyssey.service %attr(0644, root, root) /usr/lib/systemd/system/odyssey@.service %changelog odyssey-1.6.0-rc1/scripts/install_ci.sh000077500000000000000000000023051526064202200200460ustar00rootroot00000000000000#!/usr/bin/env bash set -e if ! sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'; then echo "Error adding PostgreSQL repository." exit 1 fi if ! wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -; then echo "Error adding PostgreSQL repository key." exit 1 fi if ! sudo apt-get update; then echo "Error updating package list." exit 1 fi if ! sudo apt-get -y --no-install-recommends install postgresql-14 postgresql-server-dev-14 libpq5 libpq-dev libpam0g-dev libldap-dev; then echo "Error installing PostgreSQL and its dependencies." exit 1 fi if pgrep "postgres" > /dev/null; then if ! sudo pkill -9 postgres; then echo "Error stopping PostgreSQL process." exit 1 fi fi if ! sudo sh -c 'echo -n | openssl s_client -connect https://scan.coverity.com:443 | sed -ne "/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p" >> /etc/ssl/certs/ca-certificates.crt'; then echo "Error adding SSL certificate." exit 1 fi if ! sudo apt-get clean; then echo "Error cleaning apt-get cache." exit 1 fi echo "Script completed successfully." exit 0odyssey-1.6.0-rc1/scripts/pgsql_benchmark.sh000077500000000000000000000044161526064202200210720ustar00rootroot00000000000000#!/usr/bin/env bash set -e echo "WARNING: all running instances of postgresql and odyssey will be terminated. Continue (y/n)?" read choice case "$choice" in y|Y ) echo "Ok, proceeding";; n|N ) echo "exiting" && exit 1;; * ) echo "invalid" && exit 1;; esac if pgrep -x "pgbouncer" > /dev/null then echo "PGBouncer running, please stop it" exit 1 else echo "PGBouncer is stopped, OK" fi if [[ -z $PGSRC ]]; then echo "ERROR: \$PGSRC environment variable must point to PostgreSQL source code." exit 1 fi if [[ -z $PGINSTALL ]]; then echo "ERROR: \$PGINSTALL environment variable must point to PostgreSQL installation." exit 1 fi if [ -z "$(ls -A $PGSRC)" ]; then START_WD=$PWD echo "PGSRC dir is empty, acquiring PostgreSQL sources REL_11_STABLE" git clone --depth=5 --single-branch --branch=REL_11_STABLE https://github.com/postgres/postgres $PGSRC cd $PGSRC echo "configure" ./configure --prefix=$PGINSTALL --enable-depend > /dev/null echo "make" make -j4 install>/dev/null cd $START_WD fi U=`whoami` soft_cleanup(){ pkill -9 postgres || true rm -rf tmpbuild || true } cleanup () { echo "Cleanup" soft_cleanup pkill -9 odyssey || true } cleanup trap cleanup ERR INT TERM echo "Make temp build" mkdir tmpbuild cd tmpbuild cmake -DCMAKE_BUILD_TYPE=Release ../.. >/dev/null make -j 4 >/dev/null echo "Make temp DB" $PGINSTALL/bin/initdb datadir >/dev/null $PGINSTALL/bin/pg_ctl -D datadir start >/dev/null echo "Start Odyssey" ./sources/odyssey ../../odyssey.conf>/dev/null & cd .. sleep 1 #suppress pgbench warnings in benchmarks loop PGHOST=127.0.0.1 PGPORT=6432 $PGINSTALL/bin/pgbench -i postgres declare -a resultset_sizes=("1" "100" "10000" "10000000") ## now loop through the above array for size in "${resultset_sizes[@]}" do echo "resultset_size $size" echo "select generate_series(1,$size);"> tmpbuild/shot.sql PGHOST=127.0.0.1 PGPORT=6432 $PGINSTALL/bin/pgbench --no-vacuum -T 10 -c 4 -j 4 -f tmpbuild/shot.sql postgres| grep " = " done echo "mixed resultset" echo "select generate_series(1,(random()*random()*10000)::int);"> tmpbuild/shot.sql PGHOST=127.0.0.1 PGPORT=6432 $PGINSTALL/bin/pgbench --no-vacuum -T 10 -c 4 -j 4 -f tmpbuild/shot.sql postgres| grep " = " echo "Stop Odyssey" kill %1 soft_cleanup odyssey-1.6.0-rc1/scripts/pgsql_check.sh000077500000000000000000000032021526064202200202050ustar00rootroot00000000000000#!/usr/bin/env bash set -e echo "WARNING: all running instances of postgresql and odyssey will be terminated. Continue (y/n)?" read choice case "$choice" in y|Y ) echo "Ok, proceeding";; n|N ) echo "exiting" && exit 1;; * ) echo "invalid" && exit 1;; esac if pgrep -x "pgbouncer" > /dev/null then echo "PGBouncer running, please stop it" exit 1 else echo "PGBouncer is stopped, OK" fi if [[ -z $PGSRC ]]; then echo "ERROR: \$PGSRC environment variable must point to PostgreSQL source code." exit 1 fi if [[ -z $PGINSTALL ]]; then echo "ERROR: \$PGINSTALL environment variable must point to PostgreSQL installation." exit 1 fi if [ -z "$(ls -A $PGSRC)" ]; then START_WD=$PWD echo "PGSRC dir is empty, acquiring PostgreSQL sources REL_11_STABLE" git clone --depth=5 --single-branch --branch=REL_11_STABLE https://github.com/postgres/postgres $PGSRC cd $PGSRC echo "configure" ./configure --prefix=$PGINSTALL --enable-depend > /dev/null echo "make" make -j4 install>/dev/null cd $START_WD fi U=`whoami` soft_cleanup(){ pkill -9 postgres || true rm -rf tmpbuild || true } cleanup () { echo "Cleanup" soft_cleanup pkill -9 odyssey || true } cleanup trap cleanup ERR INT TERM echo "Make temp build" mkdir tmpbuild cd tmpbuild cmake -DCMAKE_BUILD_TYPE=Release ../.. >/dev/null make -j 4 >/dev/null echo "Make temp DB" $PGINSTALL/bin/initdb datadir >/dev/null $PGINSTALL/bin/pg_ctl -D datadir start >/dev/null echo "Start Odyssey" ./sources/odyssey ../../odyssey.conf>/dev/null & cd .. PGHOST=127.0.0.1 PGPORT=6432 make -C $PGSRC installcheck echo "Stop Odyssey" kill %1 soft_cleanup odyssey-1.6.0-rc1/scripts/pgsql_pgbouncer_benchmark.sh000077500000000000000000000022021526064202200231250ustar00rootroot00000000000000#!/usr/bin/env bash set -e echo "WARNING: PGBouncer instance is expected to be running on port 6432 and connected to server. Continue (y/n)?" read choice case "$choice" in y|Y ) echo "Ok, proceeding";; n|N ) echo "exiting" && exit 1;; * ) echo "invalid" && exit 1;; esac if pgrep -x "pgbouncer" > /dev/null then echo "PGBouncer running, OK" else echo "PGBouncer is stopped, please run it" exit 1 fi #suppress pgbench warnings in benchmarks loop PGHOST=127.0.0.1 PGPORT=6432 $PGINSTALL/bin/pgbench -i postgres declare -a resultset_sizes=("1" "100" "10000" "10000000") mkdir tmpbuild ## now loop through the above array for size in "${resultset_sizes[@]}" do echo "resultset_size $size" echo "select generate_series(1,$size);"> tmpbuild/shot.sql PGHOST=127.0.0.1 PGPORT=6432 $PGINSTALL/bin/pgbench --no-vacuum -T 10 -c 4 -j 4 -f tmpbuild/shot.sql postgres| grep " = " done echo "mixed resultset" echo "select generate_series(1,(random()*random()*10000)::int);"> tmpbuild/shot.sql PGHOST=127.0.0.1 PGPORT=6432 $PGINSTALL/bin/pgbench --no-vacuum -T 10 -c 4 -j 4 -f tmpbuild/shot.sql postgres| grep " = " rm -rf tmpbuildodyssey-1.6.0-rc1/scripts/systemd/000077500000000000000000000000001526064202200170565ustar00rootroot00000000000000odyssey-1.6.0-rc1/scripts/systemd/odyssey.service000066400000000000000000000007371526064202200221460ustar00rootroot00000000000000[Unit] Description=Advanced multi-threaded PostgreSQL connection pooler and request router After=network.target [Service] User=odyssey Group=odyssey Type=notify NotifyAccess=all Restart=always ExecStartPre=/usr/bin/odyssey --test /etc/odyssey/odyssey.conf ExecStart=/usr/bin/odyssey /etc/odyssey/odyssey.conf ExecReload=/usr/bin/kill -HUP $MAINPID LimitNOFILE=100000 LimitNPROC=1024 KillSignal=SIGTERM TimeoutStopSec=30 TimeoutStartSec=10 [Install] WantedBy=multi-user.target odyssey-1.6.0-rc1/scripts/systemd/odyssey@.service000066400000000000000000000005051526064202200222370ustar00rootroot00000000000000[Unit] Description=Advanced multi-threaded PostgreSQL connection pooler and request router After=network.target [Service] User=odyssey Group=odyssey Type=notify ExecStart=/usr/bin/odyssey /etc/odyssey/%i.conf ExecReload=/usr/bin/kill -HUP $MAINPID LimitNOFILE=100000 LimitNPROC=100000 [Install] WantedBy=multi-user.target odyssey-1.6.0-rc1/scripts/update_version.sh000077500000000000000000000132321526064202200207550ustar00rootroot00000000000000#!/bin/bash # scripts/update-version.sh set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" usage() { echo "Usage: $0 [OPTIONS] [debian_revision]" echo "" echo "Options:" echo " --since TAG Compare with specific tag" echo " -h, --help Show this help" echo "" echo "Examples:" echo " $0 1.5.0 # Release" echo " $0 1.5.0 2 # Second Debian packaging" echo " $0 1.5.0-rc1 # Pre-release" echo " $0 --since v1.4.0 1.5.0 # Explicit base version" echo "" exit 1 } NEW_VERSION="" DEBIAN_REV="1" SINCE_TAG="" while [[ $# -gt 0 ]]; do case $1 in --since) SINCE_TAG="$2" shift 2 ;; --help|-h) usage ;; *) if [ -z "$NEW_VERSION" ]; then NEW_VERSION="$1" else DEBIAN_REV="$1" fi shift ;; esac done # Проверка что верÑ�иÑ� указана if [ -z "$NEW_VERSION" ]; then echo "Error: Version is required" echo "" usage fi NEW_VERSION="${NEW_VERSION#v}" echo "Updating version to: $NEW_VERSION" echo " Debian revision: $DEBIAN_REV" echo "" cd "$PROJECT_DIR" find_previous_version() { local current_version="$1" if [ -n "$SINCE_TAG" ]; then echo "$SINCE_TAG" return fi local all_tags all_tags=$(git tag --sort=-version:refname) if [[ ! "$current_version" =~ (rc|alpha|beta) ]]; then for tag in $all_tags; do tag="${tag#v}" if [[ "$tag" != "$current_version"* ]] && \ [[ ! "$tag" =~ (rc|alpha|beta) ]]; then echo "v$tag" return fi done else local base_version="${current_version%%-*}" # 1.5.0-rc1 -> 1.5.0 for tag in $all_tags; do tag="${tag#v}" if [[ "$tag" != "$base_version"* ]] && \ [[ ! "$tag" =~ (rc|alpha|beta) ]]; then echo "v$tag" return fi done fi git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "" } PREV_TAG=$(find_previous_version "$NEW_VERSION") if [ -n "$PREV_TAG" ]; then echo "Comparing with: $PREV_TAG" COMMIT_COUNT=$(git rev-list --count "$PREV_TAG..HEAD") echo " Commits since then: $COMMIT_COUNT" echo "" else echo "No previous version found, showing recent commits" PREV_TAG="HEAD~20" echo "" fi echo "Change base version for changelog?" echo " Current: $PREV_TAG" read -p "Enter different tag or press Enter to continue: " CUSTOM_TAG if [ -n "$CUSTOM_TAG" ]; then PREV_TAG="$CUSTOM_TAG" echo "Using $PREV_TAG as base" fi echo "" echo "$NEW_VERSION" > VERSION echo "Updated VERSION file" DEBIAN_VERSION="$NEW_VERSION" if [[ "$NEW_VERSION" =~ (alpha|beta|rc) ]]; then DEBIAN_VERSION="${NEW_VERSION/-/~}" fi DEBIAN_FULL_VERSION="${DEBIAN_VERSION}-${DEBIAN_REV}" TEMP_CHANGELOG=$(mktemp) cat > "$TEMP_CHANGELOG" << EOF odyssey ($DEBIAN_FULL_VERSION) unstable; urgency=medium * New upstream release $NEW_VERSION [ Changes since $PREV_TAG - edit/remove as needed ] EOF git log --pretty=format:" * %s" "$PREV_TAG..HEAD" | while IFS= read -r line; do if [[ ! "$line" =~ "Merge pull request" ]] && \ [[ ! "$line" =~ "Merge branch" ]] && \ [[ ! "$line" =~ "Release version" ]] && \ [[ ! "$line" =~ ^[[:space:]]*$ ]]; then echo "$line" >> "$TEMP_CHANGELOG" fi done cat >> "$TEMP_CHANGELOG" << EOF -- ${DEBFULLNAME:-Yandex Database Team} <${DEBEMAIL:-mdb-admin@yandex-team.ru}> $(date -R) EOF if [ -f debian/changelog ]; then cat debian/changelog >> "$TEMP_CHANGELOG" fi mkdir -p debian mv "$TEMP_CHANGELOG" debian/changelog echo "Opening editor..." echo "" echo "Tip: Group changes into categories:" echo " - New features" echo " - Bug fixes" echo " - Performance improvements" echo "" read -p "Press Enter to open editor..." ${EDITOR:-vim} debian/changelog echo "" echo "Updated debian/changelog" echo "" echo "â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�" echo "New version: $NEW_VERSION" echo "â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�" echo "" git diff --no-index /dev/null VERSION 2>/dev/null | tail -n +4 || cat VERSION echo "" echo "First changelog entry:" head -n 15 debian/changelog echo "" git status --short VERSION debian/changelog echo "" read -p "Commit these changes? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then git add VERSION debian/changelog git commit -m "Release version $NEW_VERSION" echo "Changes committed" TAG_NAME="v$NEW_VERSION" echo "" read -p "Create git tag $TAG_NAME? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then TAG_MESSAGE=$(awk '/^odyssey \(/,/^ --/' debian/changelog | head -n -1) git tag -a "$TAG_NAME" -m "$TAG_MESSAGE" echo "Tag $TAG_NAME created" echo "" echo "â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�" echo "Release $NEW_VERSION is ready!" echo "â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�â”�" echo "" echo "Push with:" echo " git push origin main" echo " git push origin $TAG_NAME" fi fi odyssey-1.6.0-rc1/scripts/vdb-views.sql000066400000000000000000000112671526064202200200240ustar00rootroot00000000000000CREATE EXTENSION dblink; CREATE SERVER odyssey FOREIGN DATA WRAPPER dblink_fdw OPTIONS (host 'localhost', port '6432', dbname 'console'); CREATE USER MAPPING FOR PUBLIC SERVER odyssey OPTIONS (user 'console' /*, password 'foobar' */); ALTER USER MAPPING FOR PUBLIC SERVER odyssey OPTIONS (set user 'console' /*, set password 'foobarbaz' */); DROP SCHEMA IF EXISTS odyssey CASCADE; CREATE SCHEMA IF NOT EXISTS odyssey; CREATE OR REPLACE VIEW odyssey.version AS SELECT * FROM dblink('odyssey', 'show version') AS _( version text ); CREATE OR REPLACE VIEW odyssey.clients AS SELECT * FROM dblink('odyssey', 'show clients') AS _( type text, "user" text, database text, state text, storage_user text, addr text, port int, local_addr text, local_port int, connect_time timestamp with time zone, request_time timestamp with time zone, wait int, wait_us int, id text, ptr text, coro int, remote_pid int, tls text ); CREATE OR REPLACE VIEW odyssey.databases AS SELECT * FROM dblink('odyssey', 'show databases') AS _( name text, host text, port int, database text, force_user text, pool_size int, reserve_pool int, pool_mode text, max_connections int, current_connections int, paused int, "disabled" int ); CREATE OR REPLACE VIEW odyssey.lists AS SELECT * FROM dblink('odyssey', 'show lists') AS _( list text, items int ); CREATE OR REPLACE VIEW odyssey.instance AS SELECT * FROM dblink('odyssey', 'show instance') AS _( list text, items int ); CREATE OR REPLACE VIEW odyssey.pools AS SELECT * FROM dblink('odyssey', 'show pools') AS _( database text, "user" text, cl_active int, cl_waiting int, sv_active int, sv_idle int, sv_used int, sv_tested int, sv_login int, maxwait int, maxwait_us int, pool_mode text ); CREATE OR REPLACE VIEW odyssey.servers AS SELECT * FROM dblink('odyssey', 'show servers') AS _( type text, "user" text, database text, state text, addr text, port int, local_addr text, local_port int, wait int, wait_us int, connect_time timestamp with time zone, request_time timestamp with time zone, ptr text, link text, remote_pid int, tls text, "offline" int ); CREATE OR REPLACE VIEW odyssey.pools_extended AS SELECT * FROM dblink('odyssey', 'show pools_extended') AS _( database text, "user" text, cl_active int, cl_waiting int, sv_active int, sv_idle int, sv_used int, sv_tested int, sv_login int, maxwait int, maxwait_us int, pool_mode text, bytes_received int, bytes_sent int, tcp_conn_count int, cl_queue int ); CREATE OR REPLACE VIEW odyssey.listen AS SELECT * FROM dblink('odyssey', 'show listen') AS _( host text, port int, tls text, tls_cert_file text, tls_key_file text, tls_ca_file text, tls_protocols text ); CREATE OR REPLACE VIEW odyssey.errors AS SELECT * FROM dblink('odyssey', 'show errors') AS _( error_type text, count int ); CREATE OR REPLACE VIEW odyssey.errors_per_route AS SELECT * FROM dblink('odyssey', 'show errors_per_route') AS _( error_type text, "user" text, "database" text, count int ); CREATE OR REPLACE VIEW odyssey.storages AS SELECT * FROM dblink('odyssey', 'show storages') AS _( type text, host text, port int, tls text, tls_cert_file text, tls_key_file text, tls_ca_file text, tls_protocols text ); CREATE OR REPLACE VIEW odyssey.rules AS SELECT * FROM dblink('odyssey', 'show rules') AS _( "database" text, "user" text, address text, connection_type text, obsolete text ); CREATE OR REPLACE VIEW odyssey.host_utilization AS SELECT * FROM dblink('odyssey', 'show host_utilization') AS _( cpu float, mem float ); GRANT USAGE ON SCHEMA odyssey TO rkhapov; GRANT USAGE ON FOREIGN SERVER odyssey TO rkhapov; GRANT SELECT ON ALL TABLES IN SCHEMA odyssey TO rkhapov;odyssey-1.6.0-rc1/sources/000077500000000000000000000000001526064202200153625ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/CMakeLists.txt000066400000000000000000000343231526064202200201270ustar00rootroot00000000000000# To make postgresql sources do not use backend functions add_definitions(-DFRONTEND) add_definitions(-D_GNU_SOURCE) option(USE_UCONTEXT "Use ucontext library for context switches" off) option(MM_MEM_PROF "Built-in memory profiling" off) option(USE_TCMALLOC "Use TCMalloc" off) option(USE_TCMALLOC_PROFILE "Use TCMalloc with profiling" off) option(ENABLE_SSL_KEYLOG "Enable OpenSSL TLS key logging support" off) set(od_binary ${CMAKE_PROJECT_NAME}) set(od_src kiwi/md5.c kiwi/options.c kiwi/var.c machinarium/ds/vrb.c machinarium/ds/hm.c machinarium/ds/vector.c machinarium/ds/queue.c machinarium/ds/heap.c machinarium/ds/intrusive/lf-stack.c machinarium/thread.c machinarium/lrand48.c machinarium/loop.c machinarium/clock.c machinarium/socket.c machinarium/stat.c machinarium/context_stack.c machinarium/context.c machinarium/coroutine.c machinarium/coroutine_cache.c machinarium/scheduler.c machinarium/call.c machinarium/signal_mgr.c machinarium/event_mgr.c machinarium/eventfd.c machinarium/machine.c machinarium/mm.c machinarium/machine_mgr.c machinarium/msg_cache.c machinarium/msg.c machinarium/mutex.c machinarium/memory.c machinarium/channel.c machinarium/channel_api.c machinarium/task_mgr.c machinarium/tls.c machinarium/io.c machinarium/close.c machinarium/connect.c machinarium/bind.c machinarium/backtrace.c machinarium/cond.c machinarium/read.c machinarium/write.c machinarium/accept.c machinarium/ring_buffer.c machinarium/dns.c machinarium/wait_list.c machinarium/wait_flag.c machinarium/wait_group.c machinarium/zpq_stream.c machinarium/compression.c machinarium/cert_hash.c machinarium/sem.c alloc/linear.c cfg/diag.c cfg/model.c cfg/keywords.c cfg/reader.c cfg/ctx.c cfg/convert.c od_memory.c affinity.c balancing.c daemon.c pid.c logger.c od_assert.c pool.c rules.c config.c dns.c route.c router.c relay.c global.c system.c stream.c scram.c cron.c worker.c tls.c attribute.c auth_query.c auth.c cancel.c client.c console.c deploy.c reset.c pstmt.c frontend.c backend.c instance.c duration.c misc.c io.c tdigest.c module.c attach.c multi_pool.c counter.c err_logger.c setproctitle.c restart_sync.c readahead.c rate.c soft_oom.c sighandler.c systemd_notify.c ejection.c thread_global.c host_watcher.c compression.c option.c tls_config.c tsa.c query.c storage.c server.c shared_pool.c address.c hba.c hba_rule.c mdb_iamproxy.c external_auth.c group.c thread_pool.c xplan.c common/base64.c common/saslprep.c common/cryptohash_openssl.c common/hmac.c common/md5.c common/scram-common.c common/fe_memutils.c common/unicode_norm.c common/wchar.c common/string.c) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND od_src machinarium/epoll.c) elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin|.*BSD") list(APPEND od_src machinarium/kqueue.c) else() message(FATAL_ERROR "Unsupported platform for machinarium poll backend: ${CMAKE_SYSTEM_NAME}") endif() message(STATUS "Poll backend: ${CMAKE_SYSTEM_NAME}") set(CONFIG_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/cfg) file(MAKE_DIRECTORY ${CONFIG_GEN_DIR}) BISON_TARGET(cfg_parser cfg/parse.y ${CONFIG_GEN_DIR}/parse.tab.c DEFINES_FILE ${CONFIG_GEN_DIR}/parse.tab.h COMPILE_FLAGS "-Werror -Wall" ) FLEX_TARGET(cfg_scanner cfg/scan.l ${CONFIG_GEN_DIR}/scan.c COMPILE_FLAGS "--header-file=${CONFIG_GEN_DIR}/scan.h" ) ADD_FLEX_BISON_DEPENDENCY(cfg_scanner cfg_parser) list(APPEND od_src ${FLEX_cfg_scanner_OUTPUTS} ${BISON_cfg_parser_OUTPUTS}) set(SQL_MINIMAL_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/sql/minimal) file(MAKE_DIRECTORY ${SQL_MINIMAL_GEN_DIR}) BISON_TARGET(sql_parser sql/minimal/parse.y ${SQL_MINIMAL_GEN_DIR}/parse.tab.c DEFINES_FILE ${SQL_MINIMAL_GEN_DIR}/parse.tab.h COMPILE_FLAGS "-Werror -Wall" ) FLEX_TARGET(sql_scanner sql/minimal/scan.l ${SQL_MINIMAL_GEN_DIR}/scan.c COMPILE_FLAGS "--header-file=${SQL_MINIMAL_GEN_DIR}/scan.h" ) ADD_FLEX_BISON_DEPENDENCY(sql_scanner sql_parser) set_source_files_properties(${FLEX_sql_scanner_OUTPUTS} PROPERTIES COMPILE_FLAGS "-Wno-unused-function" ) list(APPEND od_src ${FLEX_sql_scanner_OUTPUTS} ${BISON_sql_parser_OUTPUTS} sql/minimal/ast.c sql/minimal/ctx.c sql/minimal/keywords.c sql/minimal/parser.c ) set(CONSOLE_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/console) file(MAKE_DIRECTORY ${CONSOLE_GEN_DIR}) BISON_TARGET(console_parser console/parse.y ${CONSOLE_GEN_DIR}/parse.tab.c DEFINES_FILE ${CONSOLE_GEN_DIR}/parse.tab.h COMPILE_FLAGS "-Werror -Wall" ) FLEX_TARGET(console_scanner console/scan.l ${CONSOLE_GEN_DIR}/scan.c COMPILE_FLAGS "--header-file=${CONSOLE_GEN_DIR}/scan.h" ) ADD_FLEX_BISON_DEPENDENCY(console_scanner console_parser) set_source_files_properties(${FLEX_console_scanner_OUTPUTS} PROPERTIES COMPILE_FLAGS "-Wno-unused-function" ) list(APPEND od_src ${FLEX_console_scanner_OUTPUTS} ${BISON_console_parser_OUTPUTS} console/ast.c console/ctx.c console/keywords.c console/parser.c ) if (USE_TCMALLOC) find_library(TCMALLOC_STATIC_LIB NAMES libtcmalloc.a PATHS /usr/lib/x86_64-linux-gnu /usr/lib /usr/local/lib ) if(NOT TCMALLOC_STATIC_LIB) message(FATAL_ERROR "Static tcmalloc not found! Is libtcmalloc-dev installed?") endif() message(STATUS "TCMALLOC STATIC LIB: ${TCMALLOC_STATIC_LIB}") set(od_libraries ${od_libraries} ${TCMALLOC_STATIC_LIB} stdc++ unwind) endif() if (USE_TCMALLOC_PROFILE) find_library(TCMALLOC_PROFILER_STATIC_LIB NAMES libtcmalloc_and_profiler.a PATHS /usr/lib/x86_64-linux-gnu /usr/lib /usr/local/lib ) if(NOT TCMALLOC_PROFILER_STATIC_LIB) message(FATAL_ERROR "Static tcmalloc profiler not found!") endif() message(STATUS "TCMALLOC STATIC LIB: ${TCMALLOC_PROFILER_STATIC_LIB}") set(od_libraries ${od_libraries} ${TCMALLOC_PROFILER_STATIC_LIB} stdc++ unwind) endif() if (NOT USE_UCONTEXT) if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64") set_property(SOURCE machinarium/context_swap_x64.S PROPERTY LANGUAGE C) list(APPEND od_src machinarium/context_swap_x64.S) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86|i.86") set_property(SOURCE machinarium/context_swap_x32.S PROPERTY LANGUAGE C) list(APPEND od_src machinarium/context_swap_x32.S) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|aarch64") set_property(SOURCE machinarium/context_swap_aarch64.S PROPERTY LANGUAGE C) list(APPEND od_src machinarium/context_swap_aarch64.S) else() message(WARNING "Fallback to ucontext due to unsupported arch: ${CMAKE_SYSTEM_PROCESSOR}") set(USE_UCONTEXT on) endif() endif() message(STATUS "USE_UCONTEXT: ${USE_UCONTEXT}") message(STATUS "MM_MEM_PROF: ${MM_MEM_PROF}") message(STATUS "USE_TCMALLOC: ${USE_TCMALLOC}") message(STATUS "USE_TCMALLOC_PROFILE: ${USE_TCMALLOC_PROFILE}") configure_file("include/build.h.cmake" "build.h") configure_file("include/machinarium/build.h.cmake" "machinarium/build.h") if (PAM_FOUND) list(APPEND od_src pam.c) endif() if (LDAP_FOUND) list(APPEND od_src ldap.c) endif() if (PROM_FOUND) list(APPEND od_src prom_metrics.c) endif() include_directories("${PROJECT_SOURCE_DIR}/") include_directories("${PROJECT_SOURCE_DIR}/sources") include_directories("${PROJECT_SOURCE_DIR}/sources/include") include_directories("${PROJECT_BINARY_DIR}/") include_directories("${PROJECT_BINARY_DIR}/sources") include_directories("${PROJECT_BINARY_DIR}/sources/include") add_executable(${od_binary} ${od_src} main.c) add_dependencies(${od_binary} build_libs) target_include_directories(${od_binary} PRIVATE ${CONFIG_GEN_DIR} ${SQL_MINIMAL_GEN_DIR} ${CONSOLE_GEN_DIR}) if(THREADS_HAVE_PTHREAD_ARG) set_property(TARGET ${od_binary} PROPERTY COMPILE_OPTIONS "-pthread") set_property(TARGET ${od_binary} PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread") endif() if (ENABLE_SSL_KEYLOG) target_compile_definitions(${od_binary} PRIVATE OD_ENABLE_SSL_KEYLOG=1) endif() message(STATUS "od_libraries: ${od_libraries}") target_link_libraries(${od_binary} ${od_libraries} ${CMAKE_THREAD_LIBS_INIT}) if(NOT APPLE) target_link_libraries(${od_binary} m) endif() if (BUILD_COMPRESSION) target_link_libraries(${od_binary} ${compression_libraries}) endif() # Unit test building set(od_test_binary odyssey_test) set(od_test_src tests/odyssey_test.c tests/kiwi/test_kiwi_enquote.c tests/kiwi/test_kiwi_pgoptions.c tests/kiwi/test_kiwi_fe_read_auth.c tests/kiwi/test_kiwi_be_read_query.c tests/machinarium/test_init.c tests/machinarium/test_create0.c tests/machinarium/test_create1.c tests/machinarium/test_config.c tests/machinarium/test_context_switch.c tests/machinarium/test_sleep.c tests/machinarium/test_sleep_yield.c tests/machinarium/test_sleep_cancel0.c tests/machinarium/test_join.c tests/machinarium/test_condition0.c tests/machinarium/test_stat.c tests/machinarium/test_eventfd.c tests/machinarium/test_signal0.c tests/machinarium/test_signal1.c tests/machinarium/test_channel_create.c tests/machinarium/test_channel_rw0.c tests/machinarium/test_channel_rw1.c tests/machinarium/test_channel_rw2.c tests/machinarium/test_channel_rw3.c tests/machinarium/test_channel_rw4.c tests/machinarium/test_channel_timeout.c tests/machinarium/test_channel_cancel.c tests/machinarium/test_channel_shared_create.c tests/machinarium/test_channel_shared_rw0.c tests/machinarium/test_channel_shared_rw1.c tests/machinarium/test_channel_shared_rw2.c tests/machinarium/test_sleeplock.c tests/machinarium/test_producer_consumer0.c tests/machinarium/test_producer_consumer1.c tests/machinarium/test_producer_consumer2.c tests/machinarium/test_io_new.c tests/machinarium/test_connect.c tests/machinarium/test_connect_timeout.c tests/machinarium/test_connect_cancel0.c tests/machinarium/test_connect_cancel1.c tests/machinarium/test_accept_timeout.c tests/machinarium/test_accept_cancel.c tests/machinarium/test_advice_keepalive_usr_timeout.c tests/machinarium/test_wait_list_compare_wait_timeout.c tests/machinarium/test_wait_list_notify_after_compare_wait.c tests/machinarium/test_wait_list_compare_wait_wrong_value.c tests/machinarium/test_wait_list_without_notify.c tests/machinarium/test_wait_list_notify_after_wait.c tests/machinarium/test_wait_list_one_producer_multiple_consumers.c tests/machinarium/test_wait_list_one_producer_multiple_consumers_threads.c tests/machinarium/test_wait_list_notify_all.c tests/machinarium/test_wait_group_simple.c tests/machinarium/test_wait_group_timeout.c tests/machinarium/test_wait_group_lifetime.c tests/machinarium/test_wait_flag_simple.c tests/machinarium/test_wait_flag_timeout.c tests/machinarium/test_hm_hash.c tests/machinarium/test_getaddrinfo0.c tests/machinarium/test_getaddrinfo1.c tests/machinarium/test_getaddrinfo2.c tests/machinarium/test_client_server0.c tests/machinarium/test_client_server1.c tests/machinarium/test_client_server2.c tests/machinarium/test_client_server_unix_socket.c tests/machinarium/test_client_server_unix_socket_no_msg.c tests/machinarium/test_coroutine_names.c tests/machinarium/test_mutex_threads.c tests/machinarium/test_mutex_coroutines.c tests/machinarium/test_mutex_timeout.c tests/machinarium/test_read_10mb0.c tests/machinarium/test_read_10mb1.c tests/machinarium/test_read_10mb2.c tests/machinarium/test_read_timeout.c tests/machinarium/test_read_cancel.c tests/machinarium/test_read_var.c tests/machinarium/test_ring_buffer.c tests/machinarium/test_tls0.c tests/machinarium/test_tls_verify_error.c tests/machinarium/test_tls_cache.c tests/machinarium/test_tls_unix_socket.c tests/machinarium/test_tls_unix_socket_no_msg.c tests/machinarium/test_tls_read_10mb0.c tests/machinarium/test_tls_read_10mb1.c tests/machinarium/test_tls_read_10mb2.c tests/machinarium/test_tls_read_multithread.c tests/machinarium/test_tls_read_var.c tests/machinarium/test_tsan_simple_race_example.c tests/machinarium/test_vrb.c tests/machinarium/test_queue.c tests/machinarium/test_heap.c tests/machinarium/test_lf_stack.c tests/machinarium/test_sem.c tests/odyssey/test_attribute.c tests/odyssey/test_tdigest.c tests/odyssey/test_util.c tests/odyssey/test_hba_parse.c tests/odyssey/test_address.c tests/odyssey/test_affinity.c tests/odyssey/test_pstmt.c tests/odyssey/test_duration_parse.c tests/odyssey/test_thread_pool.c tests/odyssey/test_linear_alloc.c tests/odyssey/test_scram.c tests/odyssey/test_sql_parser.c tests/odyssey/test_console_parser.c) include_directories("${PROJECT_SOURCE_DIR}/tests") include_directories("${PROJECT_BINARY_DIR}/tests") file(COPY tests/machinarium/ca.crt DESTINATION machinarium) file(COPY tests/machinarium/client.crt DESTINATION machinarium) file(COPY tests/machinarium/client.key DESTINATION machinarium) file(COPY tests/machinarium/server.crt DESTINATION machinarium) file(COPY tests/machinarium/server.key DESTINATION machinarium) add_executable(${od_test_binary} ${od_test_src} ${od_src}) add_dependencies(${od_test_binary} build_libs) if(THREADS_HAVE_PTHREAD_ARG) set_property(TARGET ${od_test_binary} PROPERTY COMPILE_OPTIONS "-pthread") set_property(TARGET ${od_test_binary} PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread") endif() target_include_directories(${od_test_binary} PRIVATE ${CONFIG_GEN_DIR} ${SQL_MINIMAL_GEN_DIR} ${CONSOLE_GEN_DIR}) target_link_libraries(${od_test_binary} ${od_libraries} ${CMAKE_THREAD_LIBS_INIT}) if(NOT APPLE) target_link_libraries(${od_test_binary} m) endif() if (BUILD_COMPRESSION) target_link_libraries(${od_test_binary} ${compression_libraries}) endif() odyssey-1.6.0-rc1/sources/address.c000066400000000000000000000323421526064202200171570ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include od_address_range_t od_address_range_create_default(void) { od_address_range_t address_range = { .string_value = od_strdup("all"), .string_value_len = strlen("all"), .is_default = 1 }; return address_range; } void od_address_range_destroy(od_address_range_t *range) { od_free(range->string_value); } int od_address_range_copy(const od_address_range_t *src, od_address_range_t *dst) { if (dst->string_value != NULL) { od_free(dst->string_value); dst->string_value = NULL; } dst->string_value = od_strndup(src->string_value, src->string_value_len); if (dst->string_value == NULL) { return 1; } dst->string_value_len = src->string_value_len; dst->addr = src->addr; dst->mask = src->mask; dst->is_default = src->is_default; dst->is_hostname = src->is_hostname; return 0; } int od_address_range_read_prefix(od_address_range_t *address_range, char *prefix) { char *end = NULL; long len = strtol(prefix, &end, 10); if (*prefix == '\0' || *end != '\0') { return -1; } if (address_range->addr.ss_family == AF_INET) { if (len > 32) { return -1; } struct sockaddr_in *addr = (struct sockaddr_in *)&address_range->mask; uint32_t mask; if (len > 0) { mask = 0xffffffffUL << (32 - (int)len); } else { mask = 0; } addr->sin_addr.s_addr = od_bswap32(mask); return 0; } else if (address_range->addr.ss_family == AF_INET6) { if (len > 128) { return -1; } struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&address_range->mask; int i; for (i = 0; i < 16; i++) { if (len <= 0) { addr->sin6_addr.s6_addr[i] = 0; } else if (len >= 8) { addr->sin6_addr.s6_addr[i] = 0xff; } else { addr->sin6_addr.s6_addr[i] = (0xff << (8 - (int)len)) & 0xff; } len -= 8; } return 0; } return -1; } int od_address_read(struct sockaddr_storage *dest, const char *addr) { int rc; rc = inet_pton(AF_INET, addr, &((struct sockaddr_in *)dest)->sin_addr); if (rc > 0) { dest->ss_family = AF_INET; return 0; } if (inet_pton(AF_INET6, addr, &((struct sockaddr_in6 *)dest)->sin6_addr) > 0) { dest->ss_family = AF_INET6; return 0; } return -1; } static bool od_address_ipv4eq(struct sockaddr_in *a, struct sockaddr_in *b) { return (a->sin_addr.s_addr == b->sin_addr.s_addr); } static bool od_address_ipv6eq(struct sockaddr_in6 *a, struct sockaddr_in6 *b) { int i; for (i = 0; i < 16; i++) { if (a->sin6_addr.s6_addr[i] != b->sin6_addr.s6_addr[i]) { return false; } } return true; } bool od_address_equals(struct sockaddr *firstAddress, struct sockaddr *secondAddress) { if (firstAddress->sa_family == secondAddress->sa_family) { if (firstAddress->sa_family == AF_INET) { if (od_address_ipv4eq( (struct sockaddr_in *)firstAddress, (struct sockaddr_in *)secondAddress)) { return true; } } else if (firstAddress->sa_family == AF_INET6) { if (od_address_ipv6eq( (struct sockaddr_in6 *)firstAddress, (struct sockaddr_in6 *)secondAddress)) { return true; } } } return false; } bool od_address_range_equals(const od_address_range_t *first, const od_address_range_t *second) { if (first->is_hostname == second->is_hostname) { return pg_strcasecmp(first->string_value, second->string_value) == 0; } return od_address_equals((struct sockaddr *)&first->addr, (struct sockaddr *)&second->addr) && od_address_equals((struct sockaddr *)&first->mask, (struct sockaddr *)&second->mask); } static bool od_address_hostname_match(const char *pattern, const char *actual_hostname) { if (pattern[0] == '.') /* suffix match */ { size_t plen = strlen(pattern); size_t hlen = strlen(actual_hostname); if (hlen < plen) { return false; } return pg_strcasecmp(pattern, actual_hostname + (hlen - plen)) == 0; } else { return pg_strcasecmp(pattern, actual_hostname) == 0; } } /* * Check to see if a connecting IP matches a given host name. */ static bool od_address_check_hostname(struct sockaddr_storage *client_sa, const char *hostname) { struct addrinfo *gai_result, *gai; int ret; bool found; char client_hostname[NI_MAXHOST]; ret = getnameinfo((const struct sockaddr *)client_sa, sizeof(*client_sa), client_hostname, sizeof(client_hostname), NULL, 0, NI_NAMEREQD); if (ret != 0) { return false; } /* Now see if remote host name matches this pg_hba line */ if (!od_address_hostname_match(hostname, client_hostname)) { return false; } /* Lookup IP from host name and check against original IP */ ret = getaddrinfo(client_hostname, NULL, NULL, &gai_result); if (ret != 0) { return false; } found = false; for (gai = gai_result; gai; gai = gai->ai_next) { found = od_address_equals(gai->ai_addr, (struct sockaddr *)client_sa); if (found) { break; } } if (gai_result) { freeaddrinfo(gai_result); } return found; } bool od_address_validate(const od_address_range_t *address_range, struct sockaddr_storage *sa) { if (address_range->is_hostname) { return od_address_check_hostname(sa, address_range->string_value); } if (address_range->addr.ss_family != sa->ss_family) { return false; } if (sa->ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; struct sockaddr_in *addr = (struct sockaddr_in *)&address_range->addr; struct sockaddr_in *mask = (struct sockaddr_in *)&address_range->mask; in_addr_t client_addr = sin->sin_addr.s_addr; in_addr_t client_net = mask->sin_addr.s_addr & client_addr; return (client_net ^ addr->sin_addr.s_addr) == 0; } else if (sa->ss_family == AF_INET6) { struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa; struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&address_range->addr; struct sockaddr_in6 *mask = (struct sockaddr_in6 *)&address_range->mask; for (int i = 0; i < 16; ++i) { uint8_t client_net_byte = mask->sin6_addr.s6_addr[i] & sin->sin6_addr.s6_addr[i]; if (client_net_byte ^ addr->sin6_addr.s6_addr[i]) { return false; } } return true; } return false; } static inline int od_address_parse_host(char *host, od_address_t *address) { address->host = od_strdup(host); if (address->host == NULL) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline int od_address_parse_port(const char *port, od_address_t *address) { if (address->port != 0) { /* can not set twice */ return NOT_OK_RESPONSE; } errno = 0; /* to distinguish success/failure */ int val = strtol(port, NULL, 10); if (errno == ERANGE || errno == EINVAL) { return NOT_OK_RESPONSE; } if (val <= 0 || val > (1 << 16)) { return NOT_OK_RESPONSE; } address->port = val; return OK_RESPONSE; } static inline int od_address_parse_zone(const char *zone, od_address_t *address) { if (strlen(address->availability_zone) != 0) { /* can not set twice */ return NOT_OK_RESPONSE; } int len = strlen(zone); if (len > OD_MAX_AVAILABILITY_ZONE_LENGTH - 1 /* 0-byte */) { return NOT_OK_RESPONSE; } strcpy(address->availability_zone, zone); return OK_RESPONSE; } static const char *ADDRESS_TCP_PREFIX = "tcp://"; static const char *ADDRESS_UNIX_PREFIX = "unix://"; static inline bool od_address_parse_type_check_prefix(char **host, const char *prefix) { if (strncmp(prefix, *host, strlen(prefix)) == 0) { (*host) += strlen(prefix); return true; } return false; } static inline od_address_type_t od_address_parse_type(char **host) { if (od_address_parse_type_check_prefix(host, ADDRESS_TCP_PREFIX)) { return OD_ADDRESS_TYPE_TCP; } if (od_address_parse_type_check_prefix(host, ADDRESS_UNIX_PREFIX)) { return OD_ADDRESS_TYPE_UNIX; } return OD_ADDRESS_TYPE_TCP; } static inline int od_address_parse(char *buff, od_address_t *address) { char *strtok_preserve = NULL; char *token = NULL; address->type = od_address_parse_type(&buff); if (buff[0] != '[') { token = strtok_r(buff, ":", &strtok_preserve); if (token == NULL) { goto error; } if (od_address_parse_host(token, address) != OK_RESPONSE) { goto error; } token = strtok_r(NULL, ":", &strtok_preserve); } else { /* need to find ']' by ourself */ char *host = buff + 1; char *end = strchr(host, ']'); if (end == NULL) { goto error; } *end = 0; if (od_address_parse_host(host, address) != OK_RESPONSE) { goto error; } token = strtok_r(end + 1, ":", &strtok_preserve); } while (token != NULL) { if (strlen(token) == 0) { goto error; } if (isdigit(token[0])) { if (od_address_parse_port(token, address) != OK_RESPONSE) { goto error; } } else if (od_address_parse_zone(token, address) != OK_RESPONSE) { goto error; } token = strtok_r(NULL, ":", &strtok_preserve); } return OK_RESPONSE; error: od_address_destroy(address); return NOT_OK_RESPONSE; } /* Gives a rough estimate of number of endpoints, might overestimate */ static size_t od_config_reader_get_endpoints_count(const char *buff, int len) { size_t count = 1; for (int i = 0; i < len; ++i) { if (buff[i] == ',') { ++count; } } return count; } int od_parse_addresses(const char *host_str, od_address_t **out, size_t *count) { /* * parse strings like 'host(,host)*' where host is: * [address](:port)?(:availability_zone)? * examples: * klg-hostname.com * [klg-hostname.com]:1337 * [klg-hostname.com]:klg * [klg-hostname.com]:1337:klg * klg-hostname.com:1337:klg * [klg-hostname.com]:klg:1337 * klg-hostname.com,vla-hostname.com * klg-hostname.com,[vla-hostname.com]:31337 * [klg-hostname.com]:1337:klg,[vla-hostname.com]:31337:vla * * tcp://localhost:1337 * unix:///var/lib/postgresql/.s.PGSQL.5432 * tcp://localhost:1337,unix:///var/lib/postgresql/.s.PGSQL.5432 */ static __thread char buff[4096]; char *strtok_preserve = NULL; int len = strlen(host_str); if (len > (int)sizeof(buff) - 1 /* 0-byte */ || len == 0) { return NOT_OK_RESPONSE; } strcpy(buff, host_str); size_t estimate_count = od_config_reader_get_endpoints_count(buff, len); od_address_t *result = od_malloc(estimate_count * sizeof(od_address_t)); if (result == NULL) { return NOT_OK_RESPONSE; } od_address_t *address = result; char *next_address = strtok_r(buff, ",", &strtok_preserve); while (next_address != NULL) { od_address_init(address); if (od_address_parse(next_address, address) != OK_RESPONSE) { /* destroy already created addresses */ od_address_t *addr_to_free = result; while (addr_to_free != address) { od_address_destroy(addr_to_free); ++addr_to_free; } od_free(result); return NOT_OK_RESPONSE; } ++address; next_address = strtok_r(NULL, ",", &strtok_preserve); } /* strtok_r skips empty tokens, so fewer entries may be produced */ size_t produced = address - result; if (produced == 0) { od_free(result); return NOT_OK_RESPONSE; } *out = result; *count = produced; return OK_RESPONSE; } void od_address_init(od_address_t *addr) { memset(addr, 0, sizeof(od_address_t)); } void od_address_move(od_address_t *dst, od_address_t *src) { od_address_destroy(dst); memcpy(dst, src, sizeof(od_address_t)); od_address_init(src); } int od_address_copy(od_address_t *dst, const od_address_t *src) { od_address_destroy(dst); memcpy(dst, src, sizeof(od_address_t)); dst->host = od_strdup(src->host); if (dst->host == NULL) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } void od_address_destroy(od_address_t *addr) { od_free(addr->host); } static inline int od_address_unix_cmp(const od_address_t *a, const od_address_t *b) { od_assert(a->type == OD_ADDRESS_TYPE_UNIX); od_assert(b->type == OD_ADDRESS_TYPE_UNIX); return strcmp(a->host, b->host); } static inline int od_address_tcp_cmp(const od_address_t *a, const od_address_t *b) { od_assert(a->type == OD_ADDRESS_TYPE_TCP); od_assert(b->type == OD_ADDRESS_TYPE_TCP); if (a->port != b->port) { return a->port - b->port; } int zone_cmp = strcmp(a->availability_zone, b->availability_zone); if (zone_cmp != 0) { return zone_cmp; } return strcmp(a->host, b->host); } int od_address_cmp(const od_address_t *a, const od_address_t *b) { if (a->type != b->type) { return a->type - b->type; } if (a->type == OD_ADDRESS_TYPE_UNIX) { return od_address_unix_cmp(a, b); } if (a->type == OD_ADDRESS_TYPE_TCP) { return od_address_tcp_cmp(a, b); } abort(); } void od_address_to_str(const od_address_t *addr, char *out, size_t max) { if (addr->type == OD_ADDRESS_TYPE_UNIX) { od_snprintf(out, max, "unix://%s", addr->host); return; } if (addr->type == OD_ADDRESS_TYPE_TCP) { od_snprintf(out, max, "tcp://%s:%d", addr->host, addr->port); return; } abort(); } int od_address_is_localhost(const od_address_t *addr) { if (addr->type == OD_ADDRESS_TYPE_UNIX) { return 1; } if (addr->type == OD_ADDRESS_TYPE_TCP) { /* TODO: maybe use gethostbyname here */ if (strcmp(addr->host, "localhost") == 0) { return 1; } if (strcmp(addr->host, "127.0.0.1") == 0) { return 1; } if (strcmp(addr->host, "::1") == 0) { return 1; } return 0; } abort(); } odyssey-1.6.0-rc1/sources/affinity.c000066400000000000000000000217701526064202200173460ustar00rootroot00000000000000#include #include #include #include void od_affinity_cpuset_init(od_affinity_cpuset_t *set) { memset(set, 0, sizeof(od_affinity_cpuset_t)); } int od_affinity_cpuset_add(od_affinity_cpuset_t *set, int i) { if (i < 0 || i >= OD_AFFINITY_MAX_CPUS) { return -1; } int idx = i / (8 * sizeof(uint64_t)); int bit = i % (8 * sizeof(uint64_t)); set->bits[idx] |= ((uint64_t)1 << bit); return 0; } int od_affinity_cpuset_get(const od_affinity_cpuset_t *set, int i) { if (i < 0 || i >= OD_AFFINITY_MAX_CPUS) { return -1; } int idx = i / (8 * sizeof(uint64_t)); int bit = i % (8 * sizeof(uint64_t)); return (set->bits[idx] & ((uint64_t)1 << bit)) != 0; } int od_affinity_cpuset_count(const od_affinity_cpuset_t *set) { int count = 0; for (int i = 0; i < OD_AFFINITY_MAX_CPUS; ++i) { count += od_affinity_cpuset_get(set, i); } return count; } int od_affinity_cpuset_parse(const char *str, od_affinity_cpuset_t *set, char *errbuf, size_t errbuf_len) { od_affinity_cpuset_init(set); size_t len = strlen(str); const char *pos = str; const char *end = str + len; if (pos == end) { goto error; } errno = 0; while (pos < end) { if (!isdigit(*pos)) { goto error; } char *endp; long begin = strtol(pos, &endp, 10); int err_ = errno; if (err_ == EINVAL || err_ == ERANGE || begin < 0 || pos == endp) { goto error; } pos = endp; if (pos >= end) { /* single core at the end of the str */ if (od_affinity_cpuset_add(set, begin) != 0) { goto error; } return 0; } if (*pos == ',') { /* begin, */ if (od_affinity_cpuset_add(set, begin) != 0) { goto error; } ++pos; if (pos >= end) { /* trailing comma */ goto error; } continue; } if (*pos == '-') { /* begin-end */ ++pos; if (pos >= end) { goto error; } if (!isdigit(*pos)) { goto error; } long ending = strtol(pos, &endp, 10); err_ = errno; if (err_ == EINVAL || err_ == ERANGE || begin < 0 || pos == endp) { goto error; } if (ending < begin) { goto error; } for (long i = begin; i <= ending; ++i) { if (od_affinity_cpuset_add(set, i) != 0) { goto error; } } pos = endp; if (pos >= end) { continue; } if (*pos == ',') { /* * begin-ending, * skip comma */ ++pos; if (pos >= end) { /* trailing comma */ goto error; } continue; } goto error; } goto error; } return 0; error: od_affinity_cpuset_init(set); if (errbuf != NULL) { od_snprintf(errbuf, errbuf_len, "can't convert '%s' to cpuset", str); } return -1; } #if defined(__linux__) void od_affinity_cpuset_export(const od_affinity_cpuset_t *set, cpu_set_t *cpuset) { if (OD_AFFINITY_MAX_CPUS > CPU_SETSIZE) { abort(); } CPU_ZERO(cpuset); for (int i = 0; i < OD_AFFINITY_MAX_CPUS; ++i) { int rc = od_affinity_cpuset_get(set, i); if (rc) { CPU_SET(i, cpuset); } } } #endif void od_affinity_cpuset_to_str(const od_affinity_cpuset_t *set, char *out, size_t max) { int begin = -1; int prev = -1; char *pos = out; char *end = pos + max; for (int i = 0; i < OD_AFFINITY_MAX_CPUS; ++i) { int e = od_affinity_cpuset_get(set, i); if (!e) { if (begin != -1) { if (prev != begin) { pos += od_snprintf(pos, end - pos, "%d-%d,", begin, prev); } else { pos += od_snprintf(pos, end - pos, "%d,", begin); } } begin = -1; prev = -1; continue; } if (begin == -1) { begin = i; prev = i; continue; } if (prev + 1 == i) { prev = i; } else { if (prev != begin) { pos += od_snprintf(pos, end - pos, "%d-%d,", begin, prev); } else { pos += od_snprintf(pos, end - pos, "%d,", begin); } begin = i; prev = i; } } if (begin != -1) { if (prev != begin) { pos += od_snprintf(pos, end - pos, "%d-%d", begin, prev); } else { pos += od_snprintf(pos, end - pos, "%d", begin); } } } void od_affinity_rule_init(od_affinity_rule_t *rule) { memset(rule, 0, sizeof(od_affinity_rule_t)); rule->index = -1; rule->role = OD_AFFINITY_ROLE_NONE; } int od_affinity_rule_parse(const char *str, od_affinity_rule_t *rule, char *errbuf, size_t errbuf_size) { static const char *worker = "worker"; static const char *handshake = "handshake"; od_affinity_rule_init(rule); const char *pos = str; const char *end = str + strlen(str); /* read role name */ while (pos < end && isalpha(*pos)) { ++pos; } size_t rolelen = pos - str; if (rolelen == strlen(worker) && strncmp(str, worker, rolelen) == 0) { rule->role = OD_AFFINITY_ROLE_WORKER; } else if (rolelen == strlen(handshake) && strncmp(str, handshake, rolelen) == 0) { rule->role = OD_AFFINITY_ROLE_HANDSHAKE; } else { goto error; } /* read possible index */ if (pos < end && *pos == '[') { ++pos; if (pos >= end) { goto error; } char *endp; errno = 0; long index = strtol(pos, &endp, 10); int err_ = errno; if (err_ == EINVAL || err_ == ERANGE || pos == endp || index < 0 || index > INT32_MAX) { goto error; } pos = endp; rule->index = (int)index; /* check ']' */ if (pos >= end) { goto error; } if (*pos != ']') { goto error; } ++pos; } /* must read ':' */ if (pos >= end) { goto error; } if (*pos != ':') { goto error; } ++pos; /* read cpuset */ int rc = od_affinity_cpuset_parse(pos, &rule->cpuset, errbuf, errbuf_size); if (rc != 0) { goto error_ret; } return 0; error: if (errbuf != NULL) { od_snprintf(errbuf, errbuf_size, "can't parse affinity rule '%s'", str); } error_ret: od_affinity_rule_init(rule); return -1; } void od_affinity_config_init(od_affinity_config_t *config) { memset(config, 0, sizeof(od_affinity_config_t)); for (size_t i = 0; i < OD_AFFINITY_MAX_RULES; ++i) { od_affinity_rule_init(&config->rules[i]); } } static int has_rule(const od_affinity_config_t *config, const od_affinity_rule_t *rule) { for (size_t i = 0; i < config->nrules; ++i) { const od_affinity_rule_t *r = &config->rules[i]; if (rule->role == r->role && rule->index == r->index) { return 1; } } return 0; } static int parse_impl(char *str, od_affinity_config_t *config, char *errbuf, size_t errbuf_len) { if (str == NULL) { if (errbuf != NULL) { od_snprintf(errbuf, errbuf_len, "affinity config is null"); } return -1; } od_affinity_config_init(config); if (strcmp(str, "off") == 0 || strcmp(str, "no") == 0) { config->mode = OD_AFFINITY_MODE_OFF; return 0; } if (strcmp(str, "auto") == 0 || strcmp(str, "yes") == 0) { config->mode = OD_AFFINITY_MODE_AUTO; return 0; } config->mode = OD_AFFINITY_MODE_RULES; static const char *spaces = " \t"; char *save = NULL; char *rule_tok = strtok_r(str, spaces, &save); while (rule_tok != NULL) { if (config->nrules == OD_AFFINITY_MAX_RULES) { if (errbuf != NULL) { od_snprintf( errbuf, errbuf_len, "can't parse affinity config: too many rules"); } goto error; } od_affinity_rule_t rule; int rc = od_affinity_rule_parse(rule_tok, &rule, errbuf, errbuf_len); if (rc < 0) { goto error; } if (has_rule(config, &rule)) { if (errbuf != NULL) { od_snprintf(errbuf, errbuf_len, "rule '%s' duplicated", rule_tok); } goto error; } memcpy(&config->rules[config->nrules++], &rule, sizeof(od_affinity_rule_t)); rule_tok = strtok_r(NULL, spaces, &save); } if (config->nrules == 0) { if (errbuf != NULL) { od_snprintf(errbuf, errbuf_len, "can't parse affinity config: empty rules"); } goto error; } return 0; error: od_affinity_config_init(config); return -1; } int od_affinity_config_parse(const char *str, size_t len, od_affinity_config_t *config, char *errbuf, size_t errbuf_len) { char *copy = od_strndup(str, len); if (copy == NULL) { od_snprintf(errbuf, errbuf_len, "out of memory"); return -1; } int rc = parse_impl(copy, config, errbuf, errbuf_len); od_free(copy); return rc; } od_affinity_mode_t od_affinity_resolve(const od_affinity_config_t *config, od_affinity_rule_t *out, od_affinity_role_t role, int idx) { const od_affinity_rule_t *by_role_and_idx = NULL; const od_affinity_rule_t *by_role = NULL; if (config->mode == OD_AFFINITY_MODE_OFF) { return OD_AFFINITY_MODE_OFF; } if (config->mode == OD_AFFINITY_MODE_AUTO) { return OD_AFFINITY_MODE_AUTO; } for (size_t i = 0; i < config->nrules; ++i) { const od_affinity_rule_t *rule = &config->rules[i]; if (rule->role == role && rule->index == idx) { by_role_and_idx = rule; break; } if (rule->role == role && rule->index == -1) { by_role = rule; } } if (by_role_and_idx != NULL) { memcpy(out, by_role_and_idx, sizeof(od_affinity_rule_t)); return OD_AFFINITY_MODE_RULES; } if (by_role != NULL) { memcpy(out, by_role, sizeof(od_affinity_rule_t)); return OD_AFFINITY_MODE_RULES; } return OD_AFFINITY_MODE_OFF; } odyssey-1.6.0-rc1/sources/alloc/000077500000000000000000000000001526064202200164545ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/alloc/linear.c000066400000000000000000000062521526064202200200770ustar00rootroot00000000000000#include #include typedef struct { size_t size; } alloc_hdr_t; static_assert(sizeof(alloc_hdr_t) <= alignof(max_align_t), "invalid header definition"); #define UNWRAP_HDR(p) ((alloc_hdr_t *)((uint8_t *)p - alignof(max_align_t))) void od_linear_alloc_init(od_linear_alloc_t *al, uint8_t *buf, size_t size) { od_release_assert(((uintptr_t)buf & (alignof(max_align_t) - 1)) == 0); al->buf = buf; al->size = size; al->used = 0; al->track_sizes = 0; } void od_linear_alloc_reset(od_linear_alloc_t *al, int flags) { al->used = 0; al->track_sizes = (flags & OD_LINEAR_ALLOC_REALLOC) != 0; } void *od_linear_alloc_alloc(od_linear_alloc_t *al, size_t size, int flags) { /* compatibility with libc's malloc */ if (size == 0) { size = 1; } size_t align = alignof(max_align_t); size_t aligned = (size + align - 1) & ~(align - 1); size_t hdr = al->track_sizes ? align : 0; size_t total = hdr + aligned; if (al->used + total > al->size) { return NULL; } uint8_t *tail = al->buf + al->used; void *ptr = tail + hdr; al->used += total; if (al->track_sizes) { ((alloc_hdr_t *)tail)->size = aligned; } if (flags & OD_LINEAR_ALLOC_ZERO) { memset(ptr, 0, aligned); } return ptr; } void *od_linear_alloc_realloc(od_linear_alloc_t *al, void *ptr, size_t new_size) { if (ptr == NULL) { return od_linear_alloc_alloc(al, new_size, 0); } if (!al->track_sizes) { /* * without per-block headers the size of an old block is * unknown, realloc of an existing block is supported only * in OD_LINEAR_ALLOC_REALLOC mode */ return NULL; } if (new_size == 0) { od_linear_alloc_free(al, ptr); return NULL; } alloc_hdr_t *hdr = UNWRAP_HDR(ptr); size_t old_size = hdr->size; size_t align = alignof(max_align_t); new_size = (new_size + align - 1) & ~(align - 1); if (new_size == old_size) { return ptr; } /* maybe this was the last allocation and we can expand/shrink it? */ if ((al->buf + al->used) == (((uint8_t *)ptr) + old_size)) { if (old_size < new_size) { size_t diff = new_size - old_size; if (al->used + diff > al->size) { return NULL; } al->used += diff; } else { al->used -= (old_size - new_size); } hdr->size = new_size; return ptr; } void *new_ptr = od_linear_alloc_alloc(al, new_size, 0); if (new_ptr == NULL) { return NULL; } if (old_size < new_size) { memcpy(new_ptr, ptr, old_size); } else { memcpy(new_ptr, ptr, new_size); } od_linear_alloc_free(al, ptr); return new_ptr; } void *od_linear_alloc_calloc(od_linear_alloc_t *al, size_t nmemb, size_t size) { if (nmemb != 0 && size > SIZE_MAX / nmemb) { return NULL; } return od_linear_alloc_alloc(al, nmemb * size, OD_LINEAR_ALLOC_ZERO); } char *od_linear_alloc_strdup(od_linear_alloc_t *al, const char *s) { size_t len = strlen(s) + 1; void *new = od_linear_alloc_alloc(al, len, 0); if (new == NULL) { return NULL; } return (char *)memcpy(new, s, len); } char *od_linear_alloc_strndup(od_linear_alloc_t *al, const char *s, size_t n) { size_t len = strnlen(s, n); char *new = (char *)od_linear_alloc_alloc(al, len + 1, 0); if (new == NULL) { return NULL; } new[len] = '\0'; return (char *)memcpy(new, s, len); } odyssey-1.6.0-rc1/sources/attach.c000066400000000000000000000052551526064202200170010ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include od_frontend_status_t od_attach_extended_endpoint(od_instance_t *instance, char *context, od_router_t *router, od_client_t *client, od_storage_endpoint_t *endpoint) { od_router_status_t status; status = od_router_attach(router, client, 0 /* wait for idle */, endpoint, 0 /* wait if needed */); od_debug(&instance->logger, context, client, NULL, "attaching service client to backend connection status: %s", od_router_status_to_str(status)); if (status != OD_ROUTER_OK) { od_debug( &instance->logger, context, client, NULL, "failed to attach internal service client to route: %s", od_router_status_to_str(status)); return OD_EATTACH; } od_server_t *server; server = client->server; od_debug(&instance->logger, context, client, server, "attached to server %s%.*s", server->id.id_prefix, (int)sizeof(server->id.id), server->id.id); if (od_backend_not_connected(server)) { od_rule_storage_t *storage = client->rule->storage; int rc = od_backend_connect(server, context, NULL, client, storage); if (rc == NOT_OK_RESPONSE) { od_router_close(router, client); od_storage_endpoint_status_set_dead(&endpoint->status); return OD_ESERVER_CONNECT; } } int rc = od_backend_startup_preallocated(server, NULL, client); if (rc != OK_RESPONSE) { od_router_close(router, client); od_storage_endpoint_status_set_dead(&endpoint->status); return OD_ESERVER_CONNECT; } return OD_OK; } int od_attach_extended(od_instance_t *instance, char *context, od_router_t *router, od_client_t *client) { od_rule_storage_t *storage = client->rule->storage; od_endpoint_attach_candidate_t candidates[OD_STORAGE_MAX_ENDPOINTS]; od_frontend_attach_init_candidates(instance, storage, candidates, OD_TARGET_SESSION_ATTRS_ANY, 1 /* prefer localhost */); for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_t *endpoint = candidates[i].endpoint; od_frontend_status_t status = OD_EATTACH; if (candidates[i].priority >= 0) { if (client->server != NULL) { od_router_close(router, client); } status = od_attach_extended_endpoint( instance, context, router, client, endpoint); } if (status == OD_OK) { return OK_RESPONSE; } char addr[256]; od_address_to_str(&endpoint->address, addr, sizeof(addr) - 1); od_debug(&instance->logger, context, client, NULL, "attach to %s failed with status: %s", addr, od_frontend_status_to_str(status)); } return NOT_OK_RESPONSE; } odyssey-1.6.0-rc1/sources/attribute.c000066400000000000000000000030711526064202200175320ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include static int read_attribute_buf_after_key(char **data, size_t *data_size, char **out, size_t *out_size) { char *data_end = *data + *data_size; char *begin = *data; char *end; if (begin >= data_end || *begin != '=') { return -1; } begin++; end = begin; while (end < data_end && *end && *end != ',') { end++; } if (end < data_end) { *data = end + 1; } else { *data = end; } *data_size = data_end - *data; if (out) { *out = begin; } if (out_size) { *out_size = end - begin; } return 0; } int read_attribute_buf(char **data, size_t *data_size, char attr_key, char **out, size_t *out_size) { if (!*data_size || **data != attr_key) { return -1; } char *new_data = *data + 1; size_t new_data_size = *data_size - 1; if (read_attribute_buf_after_key(&new_data, &new_data_size, out, out_size) == -1) { return -1; } *data = new_data; *data_size = new_data_size; return 0; } int read_any_attribute_buf(char **data, size_t *data_size, char *attribute_ptr, char **out, size_t *out_size) { if (!*data_size) { return -1; } char attribute = **data; if (!isalpha(attribute)) { return -1; } if (attribute_ptr != NULL) { *attribute_ptr = attribute; } char *new_data = *data + 1; size_t new_data_size = *data_size - 1; if (read_attribute_buf_after_key(&new_data, &new_data_size, out, out_size) == -1) { return -1; } *data = new_data; *data_size = new_data_size; return 0; } odyssey-1.6.0-rc1/sources/auth.c000066400000000000000000001044271526064202200164770ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline int od_auth_frontend_external_authentication(od_client_t *client) { od_instance_t *instance = client->global->instance; od_route_t *route = client->route; /* AuthenticationCleartextPassword */ machine_msg_t *msg; msg = kiwi_be_write_authentication_clear_text(NULL); if (msg == NULL) { return -1; } int rc; rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for password response */ for (;;) { msg = od_read(&client->io, od_client_login_timeout(client), OD_READ_FE); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) { break; } machine_msg_free(msg); } /* read password message */ kiwi_password_t client_token; kiwi_password_init(&client_token); rc = kiwi_be_read_password(machine_msg_data(msg), machine_msg_size(msg), &client_token); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "password read error"); od_frontend_error(client, KIWI_PROTOCOL_VIOLATION, "bad password message"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } if (route->rule->enable_password_passthrough) { kiwi_password_copy(&client->received_password, &client_token); od_debug(&instance->logger, "auth", client, NULL, "saved user password to perform backend auth"); } /* support external authentication */ od_external_auth_status_t authentication_result = external_user_authentication(client->startup.user.value, client_token.password, instance, client); kiwi_password_free(&client_token); machine_msg_free(msg); if (authentication_result == OD_EAUTH_OK) { return OK_RESPONSE; } if (authentication_result == OD_EAUTH_DENIED) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect password", client->startup.database.value, client->startup.user.value); } else { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' external auth failed", client->startup.database.value, client->startup.user.value); } od_frontend_fatal(client, KIWI_INVALID_PASSWORD, "external authentication failed for user \"%s\"", client->startup.user.value); return NOT_OK_RESPONSE; } static inline int od_auth_frontend_cleartext(od_client_t *client) { od_instance_t *instance = client->global->instance; od_route_t *route = client->route; /* AuthenticationCleartextPassword */ machine_msg_t *msg; msg = kiwi_be_write_authentication_clear_text(NULL); if (msg == NULL) { return -1; } int rc; rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for password response */ for (;;) { msg = od_read(&client->io, od_client_login_timeout(client), OD_READ_FE); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) { break; } machine_msg_free(msg); } /* read password message */ kiwi_password_t client_token; kiwi_password_init(&client_token); rc = kiwi_be_read_password(machine_msg_data(msg), machine_msg_size(msg), &client_token); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "password read error"); od_frontend_error(client, KIWI_PROTOCOL_VIOLATION, "bad password message"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } if (route->rule->enable_password_passthrough) { kiwi_password_copy(&client->received_password, &client_token); od_debug(&instance->logger, "auth", client, NULL, "saved user password to perform backend auth"); } od_extension_t *extensions = client->global->extensions; /* support mdb_iamproxy authentication */ if (client->rule->enable_mdb_iamproxy_auth) { int authentication_result = mdb_iamproxy_authenticate_user( client->startup.user.value, client_token.password, instance, client); kiwi_password_free(&client_token); machine_msg_free(msg); if (authentication_result != OK_RESPONSE) { goto auth_failed; /* reference at line 80, 100 and etc */ } return OK_RESPONSE; } #ifdef LDAP_FOUND if (client->rule->ldap_endpoint_name) { od_debug(&instance->logger, "auth", client, NULL, "checking passwd against ldap endpoint %s", client->rule->ldap_endpoint_name); rc = od_auth_ldap(client, &client_token); kiwi_password_free(&client_token); machine_msg_free(msg); if (rc != OK_RESPONSE) { goto auth_failed; } return OK_RESPONSE; } #endif if (client->rule->auth_module) { od_module_t *modules = extensions->modules; /* auth callback */ od_module_t *module; module = od_modules_find(modules, client->rule->auth_module); if (module->od_auth_cleartext_cb == NULL) { kiwi_password_free(&client_token); machine_msg_free(msg); goto auth_failed; } int rc = module->od_auth_cleartext_cb(client, &client_token); kiwi_password_free(&client_token); machine_msg_free(msg); if (rc != OD_MODULE_CB_OK_RETCODE) { goto auth_failed; } return OK_RESPONSE; } #ifdef PAM_FOUND /* support PAM authentication */ if (client->rule->auth_pam_service) { od_pam_convert_passwd(client->rule->auth_pam_data, client_token.password); rc = od_pam_auth(client->rule->auth_pam_service, client->startup.user.value, client->rule->auth_pam_data, client->io.io); kiwi_password_free(&client_token); machine_msg_free(msg); if (rc == -1) { goto auth_failed; } return OK_RESPONSE; } #endif /* use remote or local password source */ kiwi_password_t client_password; if (client->rule->auth_query) { char peer[128]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); od_debug(&instance->logger, "auth", client, NULL, "running auth_query for peer %s", peer); rc = od_auth_query(client, peer); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "failed to make auth_query"); od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "failed to make auth query"); kiwi_password_free(&client_token); machine_msg_free(msg); return NOT_OK_RESPONSE; } /* TODO: consider support for empty password case. */ if (client->password.password == NULL) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect user from %s", client->startup.database.value, client->startup.user.value, peer); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect user"); kiwi_password_free(&client_token); machine_msg_free(msg); return NOT_OK_RESPONSE; } client_password = client->password; } else { client_password.password_len = client->rule->password_len + 1; client_password.password = client->rule->password; } /* authenticate */ int check = kiwi_password_compare(&client_password, &client_token); kiwi_password_free(&client_token); machine_msg_free(msg); if (check) { return OK_RESPONSE; } goto auth_failed; auth_failed: od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect password", client->startup.database.value, client->startup.user.value); od_frontend_fatal(client, KIWI_INVALID_PASSWORD, "password authentication failed for user \"%s\"", client->startup.user.value); return NOT_OK_RESPONSE; } static inline int od_auth_frontend_md5(od_client_t *client) { od_instance_t *instance = client->global->instance; /* generate salt */ uint32_t salt = kiwi_password_salt(&client->key, (uint32_t)machine_lrand48()); /* AuthenticationMD5Password */ machine_msg_t *msg; msg = kiwi_be_write_authentication_md5(NULL, (char *)&salt); if (msg == NULL) { return -1; } int rc; rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for password response */ for (;;) { msg = od_read(&client->io, od_client_login_timeout(client), OD_READ_FE); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) { break; } machine_msg_free(msg); } /* read password message */ kiwi_password_t client_token; kiwi_password_init(&client_token); rc = kiwi_be_read_password(machine_msg_data(msg), machine_msg_size(msg), &client_token); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "password read error"); od_frontend_error(client, KIWI_PROTOCOL_VIOLATION, "bad password message"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } /* use remote or local password source */ kiwi_password_t client_password; kiwi_password_init(&client_password); kiwi_password_t query_password; kiwi_password_init(&query_password); if (client->rule->auth_query) { char peer[128]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); rc = od_auth_query(client, peer); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "failed to make auth_query"); od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "failed to make auth query"); kiwi_password_free(&client_token); kiwi_password_free(&query_password); machine_msg_free(msg); return -1; } /* TODO: consider support for empty password case. */ if (client->password.password == NULL) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect user from %s", client->startup.database.value, client->startup.user.value, peer); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect user"); kiwi_password_free(&client_token); machine_msg_free(msg); return -1; } query_password = client->password; query_password.password_len = client->password.password_len - 1; } else { query_password.password_len = client->rule->password_len; query_password.password = client->rule->password; } #ifdef LDAP_FOUND if (client->rule->ldap_endpoint) { od_debug(&instance->logger, "auth", client, NULL, "checking passwd against ldap endpoint %s", client->rule->ldap_endpoint_name); rc = od_auth_ldap(client, &client_token); kiwi_password_free(&client_token); machine_msg_free(msg); if (rc != OK_RESPONSE) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect password", client->startup.database.value, client->startup.user.value); /* TODO: pass error from ldap here */ od_frontend_fatal( client, KIWI_INVALID_PASSWORD, "password authentication failed for user \"%s\"", client->startup.user.value); return NOT_OK_RESPONSE; } return OK_RESPONSE; } #endif /* prepare password hash */ rc = kiwi_password_md5(&client_password, client->startup.user.value, client->startup.user.value_len - 1, query_password.password, query_password.password_len, (char *)&salt); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "memory allocation error"); kiwi_password_free(&client_password); kiwi_password_free(&client_token); if (client->rule->auth_query) { kiwi_password_free(&query_password); } machine_msg_free(msg); return -1; } /* authenticate */ int check = kiwi_password_compare(&client_password, &client_token); kiwi_password_free(&client_password); kiwi_password_free(&client_token); machine_msg_free(msg); if (!check) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect password", client->startup.database.value, client->startup.user.value); od_frontend_fatal( client, KIWI_INVALID_PASSWORD, "password authentication failed for user \"%s\"", client->startup.user.value); return -1; } return 0; } static inline int od_auth_frontend_scram_sha_256_internal(od_client_t *client, od_scram_state_t *scram_state) { /* separated function to ensure, that scram_state will be fried properly in caller */ od_instance_t *instance = client->global->instance; char *mechanisms[2] = { "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS" }; /* request AuthenticationSASL */ machine_msg_t *msg; if (!mm_io_is_tls(client->io.io)) { msg = kiwi_be_write_authentication_sasl(NULL, mechanisms, 1); } else { msg = kiwi_be_write_authentication_sasl(NULL, mechanisms, 2); } if (msg == NULL) { return -1; } int rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for SASLInitialResponse */ for (;;) { msg = od_read(&client->io, od_client_login_timeout(client), OD_READ_FE); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) { break; } machine_msg_free(msg); } /* read the SASLInitialResponse */ char *mechanism; char *auth_data; size_t auth_data_size; rc = kiwi_be_read_authentication_sasl_initial(machine_msg_data(msg), machine_msg_size(msg), &mechanism, &auth_data, &auth_data_size); if (rc == -1) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: malformed SASLInitialResponse message"); machine_msg_free(msg); return -1; } if (strcmp(mechanism, "SCRAM-SHA-256") != 0 && strcmp(mechanism, "SCRAM-SHA-256-PLUS") != 0) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: unsupported SASL authorization mechanism"); machine_msg_free(msg); return -1; } /* use remote or local password source */ kiwi_password_t query_password; kiwi_password_init(&query_password); if (client->rule->auth_query) { char peer[128]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); rc = od_auth_query(client, peer); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "frontend auth: failed to make auth_query"); od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: failed to make auth query"); kiwi_password_free(&query_password); machine_msg_free(msg); return -1; } /* TODO: consider support for empty password case. */ if (client->password.password == NULL) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' incorrect user from %s", client->startup.database.value, client->startup.user.value, peer); od_frontend_error(client, KIWI_INVALID_PASSWORD, "incorrect user"); machine_msg_free(msg); return -1; } query_password = client->password; } else { query_password.password_len = client->rule->password_len; query_password.password = client->rule->password; } /* try to parse authentication data */ rc = od_scram_read_client_first_message(scram_state, auth_data, auth_data_size); machine_msg_free(msg); switch (rc) { case 0: break; case -1: return -1; case -2: od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: malformed SASLInitialResponse message"); return -1; case -3: od_frontend_error( client, KIWI_FEATURE_NOT_SUPPORTED, "frontend auth: doesn't support channel binding at the moment"); return -1; case -4: od_frontend_error( client, KIWI_FEATURE_NOT_SUPPORTED, "frontend auth: doesn't support authorization identity at the moment"); return -1; case OD_SASL_ERROR_MANDATORY_EXT: od_frontend_error( client, KIWI_FEATURE_NOT_SUPPORTED, "frontend auth: doesn't support mandatory extensions at the moment"); return -1; } rc = od_scram_parse_verifier(scram_state, query_password.password); if (rc == -1) { rc = od_scram_init_from_plain_password(scram_state, query_password.password); } if (rc == -1) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: invalid user password or SCRAM secret, check your config"); return -1; } msg = od_scram_create_server_first_message(scram_state); if (msg == NULL) { kiwi_password_free(&query_password); return -1; } rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } /* wait for SASLResponse */ for (;;) { msg = od_read(&client->io, od_client_login_timeout(client), OD_READ_FE); if (msg == NULL) { od_error(&instance->logger, "auth", client, NULL, "read error: %s", od_io_error(&client->io)); return -1; } kiwi_fe_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_PASSWORD_MESSAGE) { break; } machine_msg_free(msg); } /* read the SASLResponse */ rc = kiwi_be_read_authentication_sasl(machine_msg_data(msg), machine_msg_size(msg), &auth_data, &auth_data_size); if (rc == -1) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: malformed client SASLResponse"); machine_msg_free(msg); return -1; } char *final_nonce; size_t final_nonce_size; uint8_t *client_proof; rc = od_scram_read_client_final_message(client->io.io, scram_state, auth_data, auth_data_size, &final_nonce, &final_nonce_size, &client_proof); if (rc == -1) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: malformed client SASLResponse"); machine_msg_free(msg); return -1; } /* verify signatures */ rc = od_scram_verify_final_nonce(scram_state, final_nonce, final_nonce_size); if (rc == -1) { od_frontend_error( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "frontend auth: malformed client SASLResponse: nonce doesn't match"); machine_msg_free(msg); od_free(client_proof); return -1; } rc = od_scram_verify_client_proof(scram_state, client_proof); od_free(client_proof); if (rc == -1) { od_frontend_fatal( client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "password authentication failed for user \"%s\"", client->startup.user.value); machine_msg_free(msg); return -1; } machine_msg_free(msg); /* SASLFinal Message */ msg = od_scram_create_server_final_message(scram_state); if (msg == NULL) { kiwi_password_free(&query_password); return -1; } rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } return 0; } static inline int od_auth_frontend_scram_sha_256(od_client_t *client) { od_scram_state_t scram_state; od_scram_state_init(&scram_state); int rc = od_auth_frontend_scram_sha_256_internal(client, &scram_state); if (rc == 0) { memcpy(client->scram_client_key, scram_state.client_key, sizeof(scram_state.client_key)); memcpy(client->scram_server_key, scram_state.server_key, sizeof(scram_state.server_key)); client->scram_key_valid = 1; } od_scram_state_free(&scram_state); return rc; } static inline int od_auth_frontend_cert(od_client_t *client) { od_instance_t *instance = client->global->instance; if (!client->startup.is_ssl_request) { od_error(&instance->logger, "auth", client, NULL, "TLS connection required"); od_frontend_error(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "TLS connection required"); return -1; } /* compare client certificate common name */ od_route_t *route = client->route; int rc; if (route->rule->auth_common_name_default) { rc = mm_io_verify(client->io.io, route->rule->user_name); if (!rc) { return 0; } } od_list_t *i; od_list_foreach (&route->rule->auth_common_names, i) { od_rule_auth_t *auth; auth = od_container_of(i, od_rule_auth_t, link); rc = mm_io_verify(client->io.io, auth->common_name); if (!rc) { return 0; } } od_error(&instance->logger, "auth", client, NULL, "TLS certificate common name mismatch"); od_frontend_fatal(client, KIWI_INVALID_PASSWORD, "certificate authentication failed for user \"%s\"", client->startup.user.value); return -1; } static inline int od_auth_frontend_block(od_client_t *client) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' is blocked", client->startup.database.value, client->startup.user.value); od_frontend_fatal(client, KIWI_INVALID_AUTHORIZATION_SPECIFICATION, "user blocked: %s %s", client->startup.database.value, client->startup.user.value); return 0; } int od_auth_frontend(od_client_t *client) { od_instance_t *instance = client->global->instance; /* authentication mode */ int rc; switch (client->rule->auth_mode) { case OD_RULE_AUTH_CLEAR_TEXT: rc = od_auth_frontend_cleartext(client); if (rc == -1) { return -1; } break; case OD_RULE_AUTH_EXTERNAL: rc = od_auth_frontend_external_authentication(client); if (rc == -1) { return -1; } break; case OD_RULE_AUTH_MD5: rc = od_auth_frontend_md5(client); if (rc == -1) { return -1; } break; case OD_RULE_AUTH_SCRAM_SHA_256: rc = od_auth_frontend_scram_sha_256(client); if (rc == -1) { return -1; } break; case OD_RULE_AUTH_CERT: rc = od_auth_frontend_cert(client); if (rc == -1) { return -1; } break; case OD_RULE_AUTH_BLOCK: od_auth_frontend_block(client); return -1; case OD_RULE_AUTH_NONE: break; default: od_assert(0); break; } /* pass */ machine_msg_t *msg; msg = kiwi_be_write_authentication_ok(NULL); if (msg == NULL) { return -1; } rc = od_write(&client->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } return 0; } static inline int od_auth_backend_cleartext(od_server_t *server, od_client_t *client) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; od_assert(route != NULL); od_debug(&instance->logger, "auth", NULL, server, "requested clear-text authentication"); /* use storage or user password */ char *password; int password_len; if (client != NULL && client->password.password != NULL) { password = client->password.password; password_len = client->password.password_len - /* NULL */ 1; } else if (route->rule->storage_password) { password = route->rule->storage_password; password_len = route->rule->storage_password_len; } else if (route->rule->password) { password = route->rule->password; password_len = route->rule->password_len; } else if (client != NULL && client->received_password.password != NULL) { password = client->received_password.password; password_len = client->received_password.password_len - 1; } else { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } #ifdef LDAP_FOUND if (client->rule->ldap_storage_credentials_attr) { password = client->ldap_storage_password; password_len = client->ldap_storage_password_len; } #endif /* PasswordMessage */ machine_msg_t *msg; msg = kiwi_fe_write_password(NULL, password, password_len + 1); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "memory allocation error"); return -1; } int rc; rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_md5(od_server_t *server, char salt[4], od_client_t *client) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; od_assert(route != NULL); od_debug(&instance->logger, "auth", NULL, server, "requested md5 authentication"); /* use storage user or route user */ char *user; int user_len; if (route->rule->storage_user) { user = route->rule->storage_user; user_len = route->rule->storage_user_len; } else { user = route->rule->user_name; user_len = route->rule->user_name_len; } /* use storage or user password */ char *password; int password_len; if (client != NULL && client->password.password != NULL) { password = client->password.password; password_len = client->password.password_len - /* NULL */ 1; } else if (route->rule->storage_password) { password = route->rule->storage_password; password_len = route->rule->storage_password_len; } else if (route->rule->password) { password = route->rule->password; password_len = route->rule->password_len; } else if (client != NULL && client->received_password.password != NULL) { password = client->received_password.password; password_len = client->received_password.password_len - 1; } else { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } #ifdef LDAP_FOUND if (client->rule->ldap_storage_credentials_attr) { user = client->ldap_storage_username; user_len = client->ldap_storage_username_len; password = client->ldap_storage_password; password_len = client->ldap_storage_password_len; } #endif /* prepare md5 password using server supplied salt */ kiwi_password_t client_password; kiwi_password_init(&client_password); int rc; rc = kiwi_password_md5(&client_password, user, user_len, password, password_len, salt); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "memory allocation error"); kiwi_password_free(&client_password); return -1; } /* PasswordMessage */ machine_msg_t *msg; msg = kiwi_fe_write_password(NULL, client_password.password, client_password.password_len); kiwi_password_free(&client_password); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "memory allocation error"); return -1; } rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_sasl(od_server_t *server, od_client_t *client) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; od_assert(route != NULL); /* free possible stale state from previous unlucky auth */ od_scram_state_free(&server->scram_state); if (server->scram_state.client_nonce != NULL) { od_error( &instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASL was already received"); return -1; } od_debug(&instance->logger, "auth", NULL, server, "requested SASL authentication"); if (!route->rule->storage_password && !route->rule->password && (client == NULL || (!client->scram_key_valid && client->password.password == NULL)) && client->received_password.password == NULL) { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } /* SASLInitialResponse Message */ machine_msg_t *msg = od_scram_create_client_first_message(&server->scram_state); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "memory allocation error"); return -1; } int rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_sasl_continue(od_server_t *server, char *auth_data, size_t auth_data_size, od_client_t *client) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; od_assert(route != NULL); if (server->scram_state.client_nonce == NULL) { od_error(&instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASL is missing"); return -1; } if (server->scram_state.server_first_message != NULL) { od_error( &instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASLContinue was already " "received"); return -1; } /* use storage or user password */ char *password = NULL; server->scram_state.use_passthrough_keys = 0; if (route->rule->storage_password) { password = route->rule->storage_password; } else if (route->rule->password) { password = route->rule->password; } else if (client != NULL && client->scram_key_valid) { server->scram_state.use_passthrough_keys = 1; memcpy(server->scram_state.client_key, client->scram_client_key, OD_SCRAM_MAX_KEY_LEN); memcpy(server->scram_state.server_key, client->scram_server_key, OD_SCRAM_MAX_KEY_LEN); } else if (client->received_password.password) { password = client->received_password.password; } else { od_error(&instance->logger, "auth", NULL, server, "password required for route '%s.%s'", route->rule->db_name, route->rule->user_name); return -1; } #ifdef LDAP_FOUND if (client->rule->ldap_storage_credentials_attr) { password = client->ldap_storage_password; } #endif od_debug(&instance->logger, "auth", NULL, server, "continue SASL authentication %s", server->scram_state.use_passthrough_keys ? "pass-through SCRAM key" : "password"); /* SASLResponse Message */ machine_msg_t *msg = od_scram_create_client_final_message( &server->scram_state, password, auth_data, auth_data_size); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "malformed SASLResponse message"); return -1; } int rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } return 0; } static inline int od_auth_backend_sasl_final(od_server_t *server, char *auth_data, size_t auth_data_size) { od_instance_t *instance = server->global->instance; od_assert(server->route); if (server->scram_state.server_first_message == NULL) { od_error( &instance->logger, "auth", NULL, server, "unexpected message: AuthenticationSASLContinue is missing"); return -1; } od_debug(&instance->logger, "auth", NULL, server, "finishing SASL authentication"); int rc = od_scram_verify_server_signature(&server->scram_state, auth_data, auth_data_size); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "server verify failed: invalid signature"); return -1; } return 0; } int od_auth_backend(od_server_t *server, machine_msg_t *msg, od_client_t *client) { od_instance_t *instance = server->global->instance; od_assert(*(char *)machine_msg_data(msg) == KIWI_BE_AUTHENTICATION); uint32_t auth_type; char salt[4]; char *auth_data = NULL; size_t auth_data_size = 0; int rc; rc = kiwi_fe_read_auth(machine_msg_data(msg), machine_msg_size(msg), &auth_type, salt, &auth_data, &auth_data_size); if (rc == -1) { od_error(&instance->logger, "auth", NULL, server, "failed to parse authentication message"); return -1; } od_debug(&instance->logger, "auth", NULL, server, "received msg type %u", auth_type); msg = NULL; switch (auth_type) { /* AuthenticationOk */ case 0: return 0; /* AuthenticationCleartextPassword */ case 3: rc = od_auth_backend_cleartext(server, client); if (rc == -1) { return -1; } break; /* AuthenticationMD5Password */ case 5: rc = od_auth_backend_md5(server, salt, client); if (rc == -1) { return -1; } break; /* AuthenticationSASL */ case 10: rc = od_auth_backend_sasl(server, client); if (rc != OK_RESPONSE) { od_scram_state_free(&server->scram_state); } return rc; /* AuthenticationSASLContinue */ case 11: rc = od_auth_backend_sasl_continue(server, auth_data, auth_data_size, client); if (rc != OK_RESPONSE) { od_scram_state_free(&server->scram_state); } return rc; /* AuthenticationSASLFinal */ case 12: rc = od_auth_backend_sasl_final(server, auth_data, auth_data_size); od_scram_state_free(&server->scram_state); return rc; /* unsupported */ default: od_error(&instance->logger, "auth", NULL, server, "unsupported authentication method"); return -1; } /* wait for authentication response */ for (;;) { msg = od_read(&server->io, od_client_login_timeout(client), OD_READ_BE); if (msg == NULL) { od_error(&instance->logger, "auth", NULL, server, "read error: %s", od_io_error(&server->io)); return -1; } kiwi_be_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "auth", NULL, server, "%s", kiwi_be_type_to_string(type)); switch (type) { case KIWI_BE_AUTHENTICATION: rc = kiwi_fe_read_auth(machine_msg_data(msg), machine_msg_size(msg), &auth_type, salt, NULL, NULL); machine_msg_free(msg); if (rc == -1) { od_error( &instance->logger, "auth", NULL, server, "failed to parse authentication message"); return -1; } if (auth_type != 0) { od_error(&instance->logger, "auth", NULL, server, "incorrect authentication flow"); return 0; } return 0; case KIWI_BE_ERROR_RESPONSE: od_backend_error(server, "auth", machine_msg_data(msg), machine_msg_size(msg)); /* save error to fwd it to client */ server->error_connect = msg; return -1; default: machine_msg_free(msg); break; } } return 0; } odyssey-1.6.0-rc1/sources/auth_query.c000066400000000000000000000211531526064202200177160ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int od_auth_parse_passwd_from_datarow(od_logger_t *logger, machine_msg_t *msg, kiwi_password_t *result) { char *pos = (char *)machine_msg_data(msg) + 1; uint32_t pos_size = machine_msg_size(msg) - 1; /* size */ uint32_t size; int rc; rc = kiwi_read32(&size, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* count */ uint16_t count; rc = kiwi_read16(&count, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } if (count != 2) { goto error; } /* user (not used) */ uint32_t user_len; rc = kiwi_read32(&user_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } char *user = pos; rc = kiwi_readn(user_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } (void)user; (void)user_len; /* password */ /* * The length of the column value, in bytes (this count does not include itself). * Can be zero. * As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case. */ uint32_t password_len; rc = kiwi_read32(&password_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* the case of -1 */ if (password_len == UINT_MAX) { result->password = NULL; result->password_len = password_len + 1; od_debug(logger, "query", NULL, NULL, "auth query returned empty password for user %.*s", user_len, user); goto success; } if (password_len > ODYSSEY_AUTH_QUERY_MAX_PASSWORD_LEN) { goto error; } char *password = pos; rc = kiwi_readn(password_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } result->password = od_malloc(password_len + 1); if (result->password == NULL) { goto error; } memcpy(result->password, password, password_len); result->password[password_len] = 0; result->password_len = password_len + 1; success: return OK_RESPONSE; error: return NOT_OK_RESPONSE; } static od_client_t *create_auth_client(od_global_t *global, od_instance_t *instance, od_rule_t *rule) { od_router_t *router = global->router; od_client_t *client = od_client_allocate_internal(global, "auth-query"); if (client == NULL) { od_error(&instance->logger, "auth_query", NULL, NULL, "can't allocate auth client"); return NULL; } kiwi_var_set(&client->startup.user, KIWI_VAR_UNDEF, rule->auth_query_user, strlen(rule->auth_query_user) + 1); kiwi_var_set(&client->startup.database, KIWI_VAR_UNDEF, rule->auth_query_db, strlen(rule->auth_query_db) + 1); od_router_status_t status; status = od_router_route(router, client); if (status != OD_ROUTER_OK) { od_debug(&instance->logger, "auth_query", client, NULL, "failed to route internal auth query client: %s", od_router_status_to_str(status)); od_client_free_extended(client); return NULL; } int rc = od_attach_extended(instance, "auth_query", router, client); if (rc != OK_RESPONSE) { od_router_unroute(router, client); od_client_free_extended(client); return NULL; } return client; } static int do_auth_query(od_instance_t *instance, od_client_t *client, char *user, const char *query, kiwi_password_t *password) { od_server_t *server = client->server; od_assert(server != NULL); machine_msg_t *msg = od_query_do(server, "auth_query", query, user, 500); if (msg == NULL) { od_error(&instance->logger, "auth_query", client, server, "auth query returned empty msg"); return NOT_OK_RESPONSE; } int rc = od_auth_parse_passwd_from_datarow(&instance->logger, msg, password); machine_msg_free(msg); return rc; } static int get_password_from_auth_query(od_global_t *global, od_instance_t *instance, od_rule_t *rule, char *user, const char *auth_query, kiwi_password_t *password) { od_router_t *router = global->router; od_client_t *client = create_auth_client(global, instance, rule); if (client == NULL) { return NOT_OK_RESPONSE; } int rc = do_auth_query(instance, client, user, auth_query, password); od_router_close(router, client); od_router_unroute(router, client); od_client_free_extended(client); return rc; } typedef struct { od_global_t *global; od_instance_t *instance; od_rule_t *rule; od_route_t *route; char user[KIWI_MAX_VAR_SIZE]; char query[OD_QRY_MAX_SZ]; } refresh_arg_t; static void pswd_update_task(void *a) { refresh_arg_t *arg = a; od_global_t *global = arg->global; od_instance_t *instance = arg->instance; od_rule_t *rule = arg->rule; od_route_t *route = arg->route; kiwi_password_t password; memset(&password, 0, sizeof(password)); int rc = get_password_from_auth_query(global, instance, rule, arg->user, arg->query, &password); uint64_t jitter = ((uint64_t)machine_lrand48()) % 5000; uint64_t until = machine_time_ms() + 10 * 1000 + jitter; od_route_lock(route); route->auth_query_cache.valid_until_ms = machine_time_ms() + 1000; if (rc == OK_RESPONSE) { od_route_pswd_t *new = od_route_pswd_create(password.password); od_free(password.password); if (new != NULL) { od_route_pswd_unref(route->auth_query_cache.password); route->auth_query_cache.password = new; route->auth_query_cache.valid_until_ms = until; } } route->auth_query_cache.refresh_in_progress = 0; mm_wait_flag_set(route->auth_query_cache.ready); od_route_unlock(route); od_rules_unref(rule); od_free(arg); } static int run_refresh_task(od_global_t *global, od_instance_t *instance, od_rule_t *rule, od_route_t *route, od_client_t *client, char *peer) { route->auth_query_cache.refresh_in_progress = 1; od_rules_ref(rule); refresh_arg_t *arg = od_malloc(sizeof(refresh_arg_t)); if (arg == NULL) { goto error; } memset(arg, 0, sizeof(refresh_arg_t)); kiwi_var_t *user = &client->startup.user; arg->global = global; arg->instance = instance; arg->route = route; arg->rule = rule; strcpy(arg->user, user->value); char *format_pos = rule->auth_query; char *format_end = rule->auth_query + strlen(rule->auth_query); od_query_format(format_pos, format_end, user, peer, arg->query, sizeof(arg->query)); int64_t coroid = machine_coroutine_create_named(pswd_update_task, arg, "auth-query"); if (coroid == -1) { goto error; } return 0; error: od_rules_unref(rule); od_free(arg); route->auth_query_cache.refresh_in_progress = 0; int err = mm_errno_get(); od_error(&instance->logger, "auth_query", client, NULL, "can't run auth query coroutine: %d (%s)", err, strerror(err)); return -1; } int od_auth_query(od_client_t *client, char *peer) { od_global_t *global = client->global; od_instance_t *instance = global->instance; od_route_t *route = client->route; od_rule_t *rule = client->rule; od_assert(route != NULL); od_assert(rule->auth_query != NULL); od_route_lock(route); od_route_pswd_t *cached = od_route_pswd_ref(route->auth_query_cache.password); if (cached != NULL) { /* * password was queried at least once * reuse it and maybe run refresh coro in background */ uint64_t now_ms = machine_time_ms(); int outdated = route->auth_query_cache.valid_until_ms < now_ms; if (outdated && !route->auth_query_cache.refresh_in_progress) { (void)run_refresh_task(global, instance, rule, route, client, peer); } } else { /* * this is the first time password is acquired * * if no one run the refresh - do it, and the wait for ready */ int rc = 0; if (!route->auth_query_cache.refresh_in_progress) { rc = run_refresh_task(global, instance, rule, route, client, peer); } if (rc == 0) { od_route_unlock(route); mm_wait_flag_wait(route->auth_query_cache.ready, 500); od_route_lock(route); } cached = od_route_pswd_ref(route->auth_query_cache.password); if (cached == NULL) { od_error( &instance->logger, "auth_query", client, NULL, "timeout on wait for password to become available or other error"); od_route_unlock(route); return -1; } } od_route_unlock(route); if (cached->is_null) { od_route_pswd_unref(cached); return 0; } client->password.password = od_strdup(cached->value); od_route_pswd_unref(cached); if (client->password.password == NULL) { od_error(&instance->logger, "auth_query", client, NULL, "can't reuse password: OOM"); return -1; } client->password.password_len = strlen(client->password.password) + 1; return 0; } odyssey-1.6.0-rc1/sources/backend.c000066400000000000000000000736021526064202200171250ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TTT_SESSION_OWNS_TEMP_RELS_STR "ttt.owns_session_objs" void od_backend_close(od_server_t *server) { od_assert(server->route == NULL); od_assert(server->io.io == NULL); od_assert(server->tls == NULL); server->is_transaction = 0; server->idle_time = 0; kiwi_key_init(&server->key); kiwi_key_init(&server->key_client); od_server_free(server); } static inline int od_backend_terminate(od_server_t *server) { machine_msg_t *msg; msg = kiwi_fe_write_terminate(NULL); if (msg == NULL) { return -1; } return od_write(&server->io, msg); } void od_backend_close_connection(od_server_t *server) { od_assert(server != NULL); /* failed to connect to endpoint, so notring to do */ if (od_backend_not_connected(server)) { return; } if (mm_io_connected(server->io.io)) { od_backend_terminate(server); } od_io_close(&server->io); if (server->error_connect) { machine_msg_free(server->error_connect); server->error_connect = NULL; } if (server->tls) { machine_tls_free(server->tls); server->tls = NULL; } } void od_backend_error(od_server_t *server, char *context, char *data, uint32_t size) { od_route_t *route = server->route; od_rule_t *rule = route->rule; od_instance_t *instance = server->global->instance; kiwi_fe_error_t error; int rc; rc = kiwi_fe_read_error(data, size, &error); if (rc == -1) { od_error(&instance->logger, context, server->client, server, "failed to parse error message from server"); return; } od_error(&instance->logger, context, server->client, server, "%s %s %s", error.severity, error.code, error.message); if (error.detail) { od_error(&instance->logger, context, server->client, server, "DETAIL: %s", error.detail); } if (error.hint) { od_error(&instance->logger, context, server->client, server, "HINT: %s", error.hint); } if (strcmp(error.code, KIWI_OUT_OF_MEMORY) == 0) { server->oom = 1; } if (rule->pool->reserve_prepared_statement && rule->server_drop_on_cached_plan_error) { if (strcmp(error.code, KIWI_FEATURE_NOT_SUPPORTED) != 0) { return; } if (strcmp(error.message, "cached plan must not change result type") != 0) { return; } od_error( &instance->logger, context, server->client, server, "catch broken cached plan, server connection will be dropped"); server->cached_plan_broken = 1; } } int od_backend_ready(od_server_t *server, char *data, uint32_t size) { int status; int rc; rc = kiwi_fe_read_ready(data, size, &status); if (rc == -1) { return -1; } if (status == 'I') { /* no active transaction */ server->is_transaction = 0; server->is_error_tx = 0; /* * PG destroys all portals at transaction end */ if (server->client != NULL && server->client->portals != NULL) { od_client_portals_clear(server->client); } } else if (status == 'T') { /* active transaction */ server->is_transaction = 1; server->is_error_tx = 0; } else if (status == 'E') { /* error in transaction */ server->is_transaction = 1; server->is_error_tx = 1; } else { abort(); } /* update server sync reply state */ od_server_sync_reply(server); return 0; } static inline void od_backend_reset_for_retry(od_server_t *server) { od_backend_close_connection(server); od_io_free(&server->io); od_io_init(&server->io); od_scram_state_free(&server->scram_state); od_scram_state_init(&server->scram_state); kiwi_key_init(&server->key); kiwi_vars_init(&server->vars); /* * do not reset client key here * * client key can be set before real connection attempt * and if connect retry successeded, the key will be zeroed */ server->sync_request = 0; server->sync_reply = 0; server->is_transaction = 0; server->is_error_tx = 0; server->msg_broken = 0; server->oom = 0; server->synced_settings = false; server->need_startup = 1; } /* * libpq switches to the next encryption method only when the server has * answered the startup packet with a valid ErrorResponse: see CONNECTION_FAILED * in the ErrorResponse branch of CONNECTION_AWAITING_RESPONSE in PQconnectPoll. * A closed connection, a garbled reply or a peer that is not a postgres server * at all end the attempt right there, and ERRCODE_CANNOT_CONNECT_NOW is special * cased to move on to the next host instead of changing the encryption. * * On top of that we skip the retry for errors that have nothing to do with * encryption at all. libpq can afford repeating them, a pooler cannot: it would * double the connection load on a server that is already unhappy, and it would * hide the original error from the code that inspects it (see * od_frontend_error_is_too_many_connections). */ static inline int od_backend_startup_error_allows_tls_retry(od_server_t *server, const char **code) { *code = NULL; if (server->error_connect == NULL) { /* connection lost, or the peer does not talk our protocol */ return 0; } kiwi_fe_error_t error; if (kiwi_fe_read_error(machine_msg_data(server->error_connect), machine_msg_size(server->error_connect), &error) == -1) { return 0; } if (error.code == NULL) { return 0; } *code = error.code; /* as in libpq: another host may accept us, encryption is irrelevant */ if (strcmp(error.code, KIWI_CANNOT_CONNECT_NOW) == 0) { return 0; } /* * Unlike libpq: the server gave a definite answer that TLS will not * change. Drop these lines to follow libpq to the letter. */ if (strcmp(error.code, KIWI_TOO_MANY_CONNECTIONS) == 0 || strcmp(error.code, KIWI_INVALID_CATALOG_NAME) == 0 || strcmp(error.code, KIWI_INVALID_PASSWORD) == 0) { return 0; } return 1; } /* * Perform startup, falling back to a TLS connection for the "allow" mode. * * With libpq sslmode=allow the plaintext attempt comes first and the whole * connection is retried over TLS once it fails, since a server that requires * TLS refuses the plaintext one only at startup, with an ErrorResponse. */ static int od_backend_startup_with_tls_fallback(od_server_t *server, char *context, od_rule_storage_t *storage, kiwi_params_t *route_params, od_client_t *client) { od_instance_t *instance = server->global->instance; od_tls_opts_t *tlsopts = storage->tls_opts; int rc = od_backend_startup(server, route_params, client); if (rc == OK_RESPONSE) { return OK_RESPONSE; } /* * server->tls is set only once TLS has been negotiated, so the retry * below never happens more than once per connection. */ if (tlsopts->tls_mode != OD_CONFIG_TLS_ALLOW || server->tls != NULL) { return NOT_OK_RESPONSE; } /* must be checked before the reset, it frees error_connect */ const char *code = NULL; if (!od_backend_startup_error_allows_tls_retry(server, &code)) { od_debug( &instance->logger, context, client, server, "startup over a plaintext connection failed, not retrying with tls (allow), code=%s", code ? code : "(null)"); return NOT_OK_RESPONSE; } od_debug( &instance->logger, context, client, server, "startup over a plaintext connection failed (code=%s), retrying with tls (allow)", code ? code : "(null)"); /* points to server->error_connect, which can be reset at next lines */ code = NULL; od_backend_reset_for_retry(server); if (route_params != NULL) { /* drop the parameters collected by the failed attempt */ kiwi_params_free(route_params); kiwi_params_init(route_params); } rc = od_backend_connect_to(server, context, od_server_pool_address(server), tlsopts, OD_BACKEND_TLS_NEGOTIATE); if (rc != OK_RESPONSE) { return NOT_OK_RESPONSE; } return od_backend_startup(server, route_params, client); } int od_backend_startup_preallocated(od_server_t *server, kiwi_params_t *route_params, od_client_t *client) { if (!od_backend_need_startup(server)) { return OK_RESPONSE; } /* * A preallocated connection is connected long before it is started up, * so this is where the "allow" mode learns that the server wants TLS. */ return od_backend_startup_with_tls_fallback( server, "startup", server->route->rule->storage, route_params, client); } int od_backend_startup(od_server_t *server, kiwi_params_t *route_params, od_client_t *client) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; #define DEFAULT_ARGV_SIZE 6 kiwi_fe_arg_t argv[DEFAULT_ARGV_SIZE + 2 * route->rule->backend_startup_vars_sz]; kiwi_fe_arg_t default_argv[] = { { "user", 5 }, { route->id.user, route->id.user_len }, { "database", 9 }, { route->id.database, route->id.database_len }, { "replication", 12 }, { NULL, 0 } }; od_debug(&instance->logger, "startup", server->client, server, "startup server connection with user %s & database %s", route->id.user, route->id.database); for (size_t i = 0; i < route->rule->backend_startup_vars_sz; i++) { argv[i << 1].name = route->rule->backend_startup_vars[i].name; argv[i << 1].len = route->rule->backend_startup_vars[i].name_len + 1; argv[i << 1 | 1].name = route->rule->backend_startup_vars[i].value; argv[i << 1 | 1].len = route->rule->backend_startup_vars[i].value_len + 1; } int argc = route->rule->backend_startup_vars_sz * 2; for (size_t i = 0; i < DEFAULT_ARGV_SIZE; ++i) { argv[argc + i] = default_argv[i]; } argc += 4; if (client->physical_rep) { argv[argc + 1].name = "on"; argv[argc + 1].len = 3; argc += 2; } else if (client->logical_rep) { argv[argc + 1].name = "database"; argv[argc + 1].len = 9; argc += 2; } machine_msg_t *msg; msg = kiwi_fe_write_startup_message(NULL, argc, argv); if (msg == NULL) { return -1; } int rc; rc = od_write(&server->io, msg); if (rc == -1) { od_error(&instance->logger, "startup", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } /* update request count and sync state */ od_server_sync_request(server, 1); od_assert(server->client); for (;;) { msg = od_read(&server->io, UINT32_MAX, OD_READ_BE); if (msg == NULL) { od_error(&instance->logger, "startup", client, server, "read error: %s", od_io_error(&server->io)); return -1; } kiwi_be_type_t type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "startup", client, server, "received packet type: %s", kiwi_be_type_to_string(type)); switch (type) { case KIWI_BE_READY_FOR_QUERY: od_backend_ready(server, machine_msg_data(msg), machine_msg_size(msg)); server->need_startup = 0; machine_msg_free(msg); return 0; case KIWI_BE_AUTHENTICATION: rc = od_auth_backend(server, msg, client); machine_msg_free(msg); if (rc == -1) { return -1; } break; case KIWI_BE_BACKEND_KEY_DATA: rc = kiwi_fe_read_key(machine_msg_data(msg), machine_msg_size(msg), &server->key); machine_msg_free(msg); if (rc == -1) { od_error( &instance->logger, "startup", client, server, "failed to parse BackendKeyData message"); return -1; } break; case KIWI_BE_PARAMETER_STATUS: { char *name; uint32_t name_len; char *value; uint32_t value_len; rc = kiwi_fe_read_parameter(machine_msg_data(msg), machine_msg_size(msg), &name, &name_len, &value, &value_len); if (rc == -1) { machine_msg_free(msg); od_error( &instance->logger, "startup", client, server, "failed to parse ParameterStatus message"); return -1; } /* set server parameters */ if (kiwi_vars_update(&server->vars, name, name_len, value, value_len) == -1) { od_gdebug( "startup", client, server, "unexpected parameter for server: '%s', ignored", name); } if (route_params) { /* * skip volatile params * we skip in_hot_standby here because it may change * during connection lifetime, if server was * promoted */ if (name_len != sizeof("in_hot_standby") || strncmp(name, "in_hot_standby", name_len)) { kiwi_param_t *param; param = kiwi_param_allocate(name, name_len, value, value_len); if (param) { kiwi_params_add(route_params, param); } } } machine_msg_free(msg); break; } case KIWI_BE_NOTICE_RESPONSE: if (client != NULL) { int rc = od_write(&client->io, msg); if (rc != 0) { od_error( &instance->logger, "startup", client, server, "write notice to client error error: %s", od_io_error(&client->io)); return -1; } } else { machine_msg_free(msg); } break; case KIWI_BE_ERROR_RESPONSE: od_backend_error(server, "startup", machine_msg_data(msg), machine_msg_size(msg)); server->error_connect = msg; return -1; default: machine_msg_free(msg); od_debug(&instance->logger, "startup", client, server, "unexpected message: %s", kiwi_be_type_to_string(type)); return -1; } } OD_UNREACHABLE(); return 0; } static inline int od_backend_tls_negotiate(od_tls_opts_t *tlsopts, od_backend_tls_attempt_t attempt) { if (tlsopts->tls_mode == OD_CONFIG_TLS_DISABLE) { return 0; } if (attempt == OD_BACKEND_TLS_NEGOTIATE) { return 1; } /* * libpq sslmode=allow: try a plaintext connection first, TLS is * negotiated only after the server has refused the plaintext one. */ return tlsopts->tls_mode != OD_CONFIG_TLS_ALLOW; } int od_backend_connect_to(od_server_t *server, char *context, const od_address_t *address, od_tls_opts_t *tlsopts, od_backend_tls_attempt_t tls_attempt) { od_instance_t *instance = server->global->instance; od_assert(server->io.io == NULL); od_assert(address != NULL); /* create io handle */ mm_io_t *io; io = mm_io_create(); if (io == NULL) { return -1; } /* set network options */ mm_io_set_nodelay(io, instance->config.nodelay); if (instance->config.keepalive > 0) { mm_io_set_keepalive(io, 1, instance->config.keepalive, instance->config.keepalive_keep_interval, instance->config.keepalive_probes, instance->config.keepalive_usr_timeout); } int rc; rc = od_io_prepare(&server->io, io); if (rc == -1) { od_error(&instance->logger, context, NULL, server, "failed to set server io, errno = %d (%s)", machine_errno(), strerror(machine_errno())); mm_io_close(io); mm_io_free(io); return -1; } /* set tls options */ int negotiate_tls = od_backend_tls_negotiate(tlsopts, tls_attempt); if (negotiate_tls) { server->tls = od_tls_backend(tlsopts); if (server->tls == NULL) { return -1; } } /* snapshot log_session to avoid race on config reload (SIGHUP) */ int log_session = instance->config.log_session; uint64_t time_connect_start = 0; if (log_session) { time_connect_start = machine_time_us(); } struct sockaddr_un saddr_un; struct sockaddr_in saddr_v4; struct sockaddr_in6 saddr_v6; struct sockaddr *saddr; mm_addrinfo_t *ai = NULL; /* resolve server address */ if (address->type == OD_ADDRESS_TYPE_TCP) { /* assume IPv6 or IPv4 is specified */ int rc_resolve = -1; if (strchr(address->host, ':')) { /* v6 */ memset(&saddr_v6, 0, sizeof(saddr_v6)); saddr_v6.sin6_family = AF_INET6; saddr_v6.sin6_port = htons(address->port); rc_resolve = inet_pton(AF_INET6, address->host, &saddr_v6.sin6_addr); saddr = (struct sockaddr *)&saddr_v6; } else { /* v4 or hostname */ memset(&saddr_v4, 0, sizeof(saddr_v4)); saddr_v4.sin_family = AF_INET; saddr_v4.sin_port = htons(address->port); rc_resolve = inet_pton(AF_INET, address->host, &saddr_v4.sin_addr); saddr = (struct sockaddr *)&saddr_v4; } /* schedule getaddrinfo() execution */ if (rc_resolve != 1) { char rport[16]; od_snprintf(rport, sizeof(rport), "%d", address->port); rc = mm_getaddrinfo(address->host, rport, NULL, &ai, 0); if (rc != 0) { od_error(&instance->logger, context, NULL, server, "failed to resolve %s:%d", address->host, address->port); return NOT_OK_RESPONSE; } od_assert(ai != NULL); saddr = ai->ai_addr; } /* connected */ } else { /* set unix socket path */ memset(&saddr_un, 0, sizeof(saddr_un)); saddr_un.sun_family = AF_UNIX; saddr = (struct sockaddr *)&saddr_un; od_snprintf(saddr_un.sun_path, sizeof(saddr_un.sun_path), "%s", address->host); } uint64_t time_resolve = 0; if (log_session) { time_resolve = machine_time_us() - time_connect_start; } /* connect to server */ if (ai != NULL) { for (mm_addrinfo_t *cur = ai; cur != NULL; cur = cur->ai_next) { saddr = cur->ai_addr; rc = mm_io_connect(server->io.io, saddr, (uint32_t)instance->config .backend_connect_timeout_ms); if (rc == 0) { break; } char addr_str[INET6_ADDRSTRLEN] = ""; if (cur->ai_family == AF_INET) { inet_ntop(AF_INET, &((struct sockaddr_in *)cur->ai_addr) ->sin_addr, addr_str, sizeof(addr_str)); } else if (cur->ai_family == AF_INET6) { inet_ntop(AF_INET6, &((struct sockaddr_in6 *)cur->ai_addr) ->sin6_addr, addr_str, sizeof(addr_str)); } int err = mm_errno_get(); od_error( &instance->logger, context, server->client, server, "failed to connect to %s:%d, errno=%d (%s), trying next address...", addr_str, address->port, err, strerror(err)); } mm_freeaddrinfo(ai); } else { rc = mm_io_connect( server->io.io, saddr, (uint32_t)instance->config.backend_connect_timeout_ms); } if (rc == NOT_OK_RESPONSE) { if (address->type == OD_ADDRESS_TYPE_TCP) { od_error(&instance->logger, context, server->client, server, "failed to connect to %s:%d, errno=%d (%s)", address->host, address->port, machine_errno(), strerror(machine_errno())); } else { od_error(&instance->logger, context, server->client, server, "failed to connect to %s, errno=%d, (%s)", saddr_un.sun_path, machine_errno(), strerror(machine_errno())); } return NOT_OK_RESPONSE; } /* do tls handshake */ if (negotiate_tls) { rc = od_tls_backend_connect(server, &instance->logger, tlsopts); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } } uint64_t time_connect = 0; if (log_session) { time_connect = machine_time_us() - time_connect_start; } /* log server connection */ if (log_session) { char addr_buff[256]; mm_io_format_socket_addr(server->io.io, addr_buff, sizeof(addr_buff)); if (address->type == OD_ADDRESS_TYPE_TCP) { od_log(&instance->logger, context, server->client, server, "new server connection %s -> %s:%d (connect time: %d usec, " "resolve time: %d usec)", addr_buff, address->host, address->port, (int)time_connect, (int)time_resolve); } else { od_log(&instance->logger, context, server->client, server, "new server connection %s -> %s (connect time: %d usec, resolve " "time: %d usec)", addr_buff, saddr_un.sun_path, (int)time_connect, (int)time_resolve); } } server->need_startup = 1; return 0; } static inline int od_storage_parse_rw_check_response(machine_msg_t *msg, bool *is_rw) { char *pos = (char *)machine_msg_data(msg) + 1; uint32_t pos_size = machine_msg_size(msg) - 1; /* size */ uint32_t size; int rc; rc = kiwi_read32(&size, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* count */ uint16_t count; rc = kiwi_read16(&count, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } if (count != 1) { goto error; } /* (not used) */ uint32_t resp_len; rc = kiwi_read32(&resp_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* we expect exactly one row */ if (resp_len != 1) { return NOT_OK_RESPONSE; } /* pg is in recovery false means db is open for write */ if (pos[0] == 'f') { *is_rw = true; return OK_RESPONSE; } else if (pos[0] == 't') { *is_rw = false; return OK_RESPONSE; } /* fallthrough to error */ error: return NOT_OK_RESPONSE; } static inline int64_t strtol_safe(const char *s, int len) { char buff[32]; memset(buff, 0, sizeof(buff)); if (len <= 0) { return 0; } if (len >= (int)sizeof(buff)) { len = sizeof(buff) - 1; } memcpy(buff, s, len); return strtoll(buff, NULL, 0); } static inline int parse_lag_from_datarow(machine_msg_t *msg, int64_t *repl_lag) { char *pos = (char *)machine_msg_data(msg) + 1; uint32_t pos_size = machine_msg_size(msg) - 1; /* size */ uint32_t size; int rc; rc = kiwi_read32(&size, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* count */ uint16_t count; rc = kiwi_read16(&count, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } if (count != 1) { goto error; } uint32_t lag_len; rc = kiwi_read32(&lag_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* SQL NULL is encoded as length = -1 (0xFFFFFFFF in uint32_t) */ if (lag_len == (uint32_t)-1) { *repl_lag = 0; return OK_RESPONSE; } *repl_lag = strtol_safe(pos, (int)lag_len); return OK_RESPONSE; error: return NOT_OK_RESPONSE; } int od_backend_update_endpoint_status(od_instance_t *instance, od_client_t *client, od_server_t *server, char *context, const char *lag_query, od_storage_endpoint_t *endpoint) { char addr[256]; od_address_to_str(&endpoint->address, addr, sizeof(addr) - 1); od_storage_endpoint_status_t status; od_storage_endpoint_status_get(&endpoint->status, &status); machine_msg_t *msg; if (lag_query != NULL) { msg = od_query_do(server, context, lag_query, NULL, 1000); if (msg == NULL) { od_error( &instance->logger, context, client, server, "receive msg failed, closing backend connection"); return NOT_OK_RESPONSE; } int64_t last_heartbeat; int rc = parse_lag_from_datarow(msg, &last_heartbeat); machine_msg_free(msg); msg = NULL; if (rc == 0) { int64_t lag = machine_timeofday_sec() - last_heartbeat; if (lag < 0) { lag = 0; } status.repl_lag_sec = lag; od_debug( &instance->logger, context, client, server, "replica lag time of %s is updated to %" PRId64, addr, status.repl_lag_sec); } else { od_error(&instance->logger, context, client, server, "can't parse lag"); return NOT_OK_RESPONSE; } } msg = od_query_do(server, context, "SELECT pg_is_in_recovery()", NULL, 1000); if (msg == NULL) { od_error(&instance->logger, context, client, server, "can't execute pg_is_in_recovery"); return NOT_OK_RESPONSE; } if (od_storage_parse_rw_check_response(msg, &status.is_read_write) != OK_RESPONSE) { od_error(&instance->logger, context, client, server, "can't parse pg_is_in_recovery result"); machine_msg_free(msg); return NOT_OK_RESPONSE; } machine_msg_free(msg); status.last_update_time_ms = machine_time_ms(); status.alive = 1; od_storage_endpoint_status_set(&endpoint->status, &status); od_debug(&instance->logger, context, client, server, "read-write status of '%s' is updated to '%s'", addr, status.is_read_write ? "true" : "false"); return OK_RESPONSE; } od_tsa_check_result_t od_backend_check_tsa(od_rule_storage_t *storage, od_storage_endpoint_t *endpoint, char *context, od_server_t *server, od_client_t *client, od_target_session_attrs_t attrs) { if (attrs == OD_TARGET_SESSION_ATTRS_ANY) { return OD_TSA_CHECK_OK; } od_global_t *global = server->global; od_instance_t *instance = global->instance; if (od_storage_endpoint_status_is_outdated( &endpoint->status, storage->endpoints_status_poll_interval_ms)) { if (od_backend_update_endpoint_status( instance, client, server, context, NULL /* do not update lag */, endpoint) != OK_RESPONSE) { return OD_TSA_CHECK_BACKEND_ERROR; } } od_storage_endpoint_status_t status; od_storage_endpoint_status_get(&endpoint->status, &status); if (!od_tsa_match_rw_state(attrs, status.is_read_write)) { return OD_TSA_CHECK_FAIL; } return OD_TSA_CHECK_OK; } static inline int od_backend_connect_on_server_address( od_rule_storage_t *storage, od_server_t *server, char *context, kiwi_params_t *route_params, od_client_t *client) { const od_address_t *address = od_server_pool_address(server); od_retcode_t rc; rc = od_backend_connect_to(server, context, address, storage->tls_opts, OD_BACKEND_TLS_DEFAULT); if (rc == NOT_OK_RESPONSE) { return rc; } /* send startup and do initial configuration */ rc = od_backend_startup_with_tls_fallback(server, context, storage, route_params, client); if (rc == NOT_OK_RESPONSE) { return rc; } return OK_RESPONSE; } int od_backend_connect(od_server_t *server, char *context, kiwi_params_t *route_params, od_client_t *client, od_rule_storage_t *storage) { od_assert(server->route != NULL); return od_backend_connect_on_server_address(storage, server, context, route_params, client); } int od_backend_connect_cancel(od_server_t *server, od_rule_storage_t *storage, const od_address_t *address, kiwi_key_t *key) { od_instance_t *instance = server->global->instance; /* * Use a bounded timeout — cancel is best-effort. * UINT32_MAX caused coroutines to hang forever if the backend * closed the connection in a way that machinarium didn't wake * the reader, leaking routing_clients counter and socket fds. * Configurable via cancel_timeout_ms (default 1000). * A value of 0 means no timeout (UINT32_MAX) — not recommended. */ uint32_t cancel_timeout = instance->config.cancel_timeout_ms > 0 ? (uint32_t)instance->config.cancel_timeout_ms : UINT32_MAX; /* * Connect to server. * * A cancel connection sends no startup packet, so the "allow" mode has * nothing to retry on and stays plaintext here. That is enough: the * postmaster handles CancelRequest before authentication and before * pg_hba.conf is consulted, so a plaintext cancel is delivered even by * a server that accepts hostssl connections only. The cancel key is * sent in cleartext then, exactly like the signal-safe PQcancel() of * libpq does it. */ int rc; rc = od_backend_connect_to(server, "cancel", address, storage->tls_opts, OD_BACKEND_TLS_DEFAULT); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* send cancel request */ machine_msg_t *msg; msg = kiwi_fe_write_cancel(NULL, key->key_pid, key->key); if (msg == NULL) { return -1; } rc = od_write2(&server->io, msg, cancel_timeout); if (rc == -1) { od_error(&instance->logger, "cancel", NULL, NULL, "write error: %s", od_io_error(&server->io)); return -1; } /* * wait the pg to process the cancel request * modern pg uses pqFlush, which falls to send + read * but there is no that powerful function in mm * so just do the things older pg did - it will work too * https://github.com/postgres/postgres/blob/REL_16_0/src/interfaces/libpq/fe-connect.c#L4946-L4960 */ machine_msg_t *unused = od_read(&server->io, cancel_timeout, OD_READ_BE); if (unused != NULL) { machine_msg_free(unused); } return 0; } int od_backend_update_parameter(od_server_t *server, char *context, char *data, uint32_t size, int server_only) { od_instance_t *instance = server->global->instance; od_client_t *client = server->client; char *name; uint32_t name_len; char *value; uint32_t value_len; int rc; rc = kiwi_fe_read_parameter(data, size, &name, &name_len, &value, &value_len); if (rc == -1) { od_error(&instance->logger, context, NULL, server, "failed to parse ParameterStatus message"); return -1; } /* update server only or client and server parameter */ od_debug(&instance->logger, context, client, server, "%.*s = %.*s", name_len, name, value_len, value); /* ttt reports temp-rel ownership via ParameterStatus */ if (client != NULL && name_len == sizeof(TTT_SESSION_OWNS_TEMP_RELS_STR) && memcmp(name, TTT_SESSION_OWNS_TEMP_RELS_STR, sizeof(TTT_SESSION_OWNS_TEMP_RELS_STR)) == 0) { client->backend_pin = value_len == 3 && memcmp(value, "on", 2) == 0; od_debug(&instance->logger, context, client, server, "ttt: backend_pin set to %d", client->backend_pin); } if (server_only) { kiwi_vars_update(&server->vars, name, name_len, value, value_len); } else { kiwi_vars_update_both(&client->vars, &server->vars, name, name_len, value, value_len); } return 0; } int od_backend_ready_wait(od_server_t *server, char *ctx, uint32_t time_ms) { od_frontend_status_t st = od_service_stream_server_until_rfq( ctx, server, 0 /* ignore_errors */, time_ms); if (st != OD_OK) { return -1; } return 0; } od_retcode_t od_backend_query_send(od_server_t *server, char *context, const char *query, char *param, int len, uint32_t timeout_ms) { od_instance_t *instance = server->global->instance; machine_msg_t *msg; if (param) { msg = kiwi_fe_write_prep_stmt(NULL, query, param); } else { msg = kiwi_fe_write_query(NULL, query, len); } if (msg == NULL) { return NOT_OK_RESPONSE; } int rc; rc = od_write2(&server->io, msg, timeout_ms); if (rc == -1) { od_error(&instance->logger, context, server->client, server, "write error: %s", od_io_error(&server->io)); return NOT_OK_RESPONSE; } /* update server sync state */ od_server_sync_request(server, 1); od_assert(server->client); return OK_RESPONSE; } od_retcode_t od_backend_query(od_server_t *server, char *context, char *query, char *param, int len, uint32_t timeout) { if (od_backend_query_send(server, context, query, param, len, timeout) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } od_retcode_t rc = od_backend_ready_wait(server, context, timeout); return rc; } int od_backend_not_connected(od_server_t *server) { return server->io.io == NULL; } int od_backend_need_startup(od_server_t *server) { return server->need_startup; } odyssey-1.6.0-rc1/sources/balancing.c000066400000000000000000000222301526064202200174430ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #define METHOD_RR_STR "roundrobin" #define METHOD_LEASTCONN_STR "leastconn" #define METHOD_WEIGHTED_STR "weighted" #define METHOD_WEIGHTED_LEASTCONN "weightedleastconn" #define METHOD_RESPONSETIME "responsetime" od_balancing_method_t od_balancing_method_from_str(const char *str, size_t len) { if (len == strlen(METHOD_RR_STR) && strncasecmp(METHOD_RR_STR, str, len) == 0) { return OD_BALANCING_METHOD_ROUNDROBIN; } if (len == strlen(METHOD_LEASTCONN_STR) && strncasecmp(METHOD_LEASTCONN_STR, str, len) == 0) { return OD_BALANCING_METHOD_LEASTCONN; } if (len == strlen(METHOD_WEIGHTED_STR) && strncasecmp(METHOD_WEIGHTED_STR, str, len) == 0) { return OD_BALANCING_METHOD_WEIGHTED; } if (len == strlen(METHOD_WEIGHTED_LEASTCONN) && strncasecmp(METHOD_WEIGHTED_LEASTCONN, str, len) == 0) { return OD_BALANCING_METHOD_WEIGHTED_LEASTCONN; } if (len == strlen(METHOD_RESPONSETIME) && strncasecmp(METHOD_RESPONSETIME, str, len) == 0) { return OD_BALANCING_METHOD_RESPONSETIME; } return OD_BALANCING_METHOD_UNDEF; } const char *od_balancing_method_to_str(od_balancing_method_t method) { switch (method) { case OD_BALANCING_METHOD_ROUNDROBIN: return METHOD_RR_STR; case OD_BALANCING_METHOD_LEASTCONN: return METHOD_LEASTCONN_STR; case OD_BALANCING_METHOD_WEIGHTED: return METHOD_WEIGHTED_STR; case OD_BALANCING_METHOD_WEIGHTED_LEASTCONN: return METHOD_WEIGHTED_LEASTCONN; case OD_BALANCING_METHOD_RESPONSETIME: return METHOD_RESPONSETIME; case OD_BALANCING_METHOD_UNDEF: return "no specified"; default: od_abort(); } } void od_storage_balancing_init(od_storage_balancing_t *b) { memset(b, 0, sizeof(od_storage_balancing_t)); b->method.type = OD_BALANCING_METHOD_UNDEF; b->method.az_aware = 1; atomic_init(&b->method.roundrobin.counter, 0); b->debug_notice = 0; } void od_storage_balancing_destroy(od_storage_balancing_t *b) { od_free(b->method.weighted.weights); od_free(b->method.weighted_leastconn.weights); } void od_storage_balancing_copy(od_storage_balancing_t *dest, const od_storage_balancing_t *src) { memcpy(dest, src, sizeof(od_storage_balancing_t)); } static int in_same_az(od_instance_t *instance, const od_storage_endpoint_t *endp) { return strcmp(instance->config.availability_zone, endp->address.availability_zone) == 0; } #define RR_PACK(l, g) (((((uint64_t)(l)) << 32) | (uint64_t)(g))) #define RR_LOCAL_UNPACK(v) ((uint32_t)((v) >> 32)) #define RR_GLOBAL_UNPACK(v) ((uint32_t)((v) & 0xffffffffu)) static void rr_next(od_method_roundroubin_t *rr, uint32_t *local, uint32_t *global) { while (1) { uint64_t curr = atomic_load(&rr->counter); uint32_t l = RR_LOCAL_UNPACK(curr); uint32_t g = RR_GLOBAL_UNPACK(curr); uint64_t new = RR_PACK((l + 1) & 0xffffffffu, (g + 1) & 0xffffffffu); if (atomic_compare_exchange_strong(&rr->counter, &curr, new)) { *local = l; *global = g; return; } } } static size_t roundrobin(od_storage_balancing_t *b, od_rule_storage_t *storage, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg) { /* * assume the endpoints list contains several hosts, * some of them is in same AZ(+) as instance is, some * in different AZ(-): * [ h1+, h2-, h3-, h4+, h5-, h6- ] * * lets stable-sort them by AZ locality * (with O(n^2) algo, assuming there are no many endpoints): * [ h1+, h4+, h2-, h3-, h5-, h6- ] * * and then let it be two RR counters: for local and global hosts * [ h1+, h4+, h2-, h3-, h5-, h6- ] * ^ ^ * (AZ idx 1) (non-AZ idx 2) * * after getting corresponding indexes, lets * rotate each subarray to get resulting * round robin order of connection: * [ h4+, h1+, h5-, h6-, h2-, h3- ] */ od_assert(storage->endpoints_count > 1); od_instance_t *instance = od_global_get_instance(); /* * round-robin counter always comes from the storage so that * multiple listen endpoints referencing the same storage share it, * ensuring fair distribution. az_aware flag (and any other * per-listen override) is taken from the effective balancing struct */ uint32_t local, global; rr_next(&storage->balancing.method.roundrobin, &local, &global); size_t count = 0; for (size_t i = 0; i < storage->endpoints_count && count < max; ++i) { od_storage_endpoint_t *e = &storage->endpoints[i]; if (filter != NULL && !filter(e, arg)) { continue; } out[count++] = e; } size_t first_global = 0; size_t nlocal = 0; size_t nglobal = 0; for (size_t i = 0; i < count; ++i) { od_storage_endpoint_t *e = out[i]; if (b->method.az_aware && !in_same_az(instance, e)) { ++nglobal; continue; } ++nlocal; if (i != first_global) { size_t n = i - first_global; memmove(&out[first_global + 1], &out[first_global], n * sizeof(out[0])); out[first_global] = e; } ++first_global; } if (nlocal > 0) { od_array_rotate_left_p((void **)out, nlocal, local); } if (nglobal > 0) { od_array_rotate_left_p((void **)(&out[nlocal]), nglobal, global); } return count; } static int addr_filter(void *arg, const od_multi_pool_key_t *key) { const od_storage_endpoint_t *endp = (const od_storage_endpoint_t *)arg; return od_address_cmp(&key->address, &endp->address) == 0; } typedef struct { od_route_t *route; od_storage_balancing_t *b; } leastconn_arg_t; static int conncount_cmp(void *arg, const void *a, const void *b) { leastconn_arg_t *arg_ = (leastconn_arg_t *)arg; od_route_t *route = arg_->route; od_storage_balancing_t *balancing = arg_->b; od_instance_t *instance = od_global_get_instance(); const od_storage_endpoint_t *f = (const od_storage_endpoint_t *)a; const od_storage_endpoint_t *s = (const od_storage_endpoint_t *)b; if (f == s || od_address_cmp(&f->address, &s->address) == 0) { return 0; } int f_az = !balancing->method.az_aware || in_same_az(instance, f); int s_az = !balancing->method.az_aware || in_same_az(instance, s); if (f_az != s_az) { /* let az-local hosts be the first */ return s_az - f_az; } od_multi_pool_t *mp = od_route_server_pools(route); int f_cnt = od_multi_pool_total_locked(mp, addr_filter, (void *)f); int s_cnt = od_multi_pool_total_locked(mp, addr_filter, (void *)s); /* let less count be the first */ return f_cnt - s_cnt; } static size_t leastconn(od_storage_balancing_t *b, od_route_t *route, od_rule_storage_t *storage, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg) { (void)b; size_t count = 0; for (size_t i = 0; i < storage->endpoints_count && count < max; ++i) { od_storage_endpoint_t *e = &storage->endpoints[i]; if (filter != NULL && !filter(e, arg)) { continue; } out[count++] = e; } /* * get the lock so we have a consistent conn counts * TODO: do not do it? */ od_route_lock(route); leastconn_arg_t sarg; sarg.route = route; sarg.b = b; od_insertion_sort_p((void **)out, count, conncount_cmp, &sarg); od_route_unlock(route); return count; } static size_t weighted(od_storage_balancing_t *b, od_route_t *route, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg) { (void)b; (void)route; (void)out; (void)max; (void)filter; (void)arg; /* not implemented */ abort(); } static size_t weighted_leastconn(od_storage_balancing_t *b, od_route_t *route, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg) { (void)b; (void)route; (void)out; (void)max; (void)filter; (void)arg; /* not implemented */ abort(); } static size_t responsetime(od_storage_balancing_t *b, od_route_t *route, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg) { (void)b; (void)route; (void)out; (void)max; (void)filter; (void)arg; /* not implemented */ abort(); } size_t od_storage_balancing_select(od_storage_balancing_t *b, od_rule_storage_t *storage, od_route_t *route, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg) { if (max == 0 || storage->endpoints_count == 0) { return 0; } if (storage->endpoints_count == 1) { out[0] = &storage->endpoints[0]; return 1; } switch (b->method.type) { case OD_BALANCING_METHOD_UNDEF: case OD_BALANCING_METHOD_ROUNDROBIN: return roundrobin(b, storage, out, max, filter, arg); case OD_BALANCING_METHOD_LEASTCONN: return leastconn(b, route, storage, out, max, filter, arg); case OD_BALANCING_METHOD_WEIGHTED: return weighted(b, route, out, max, filter, arg); case OD_BALANCING_METHOD_WEIGHTED_LEASTCONN: return weighted_leastconn(b, route, out, max, filter, arg); case OD_BALANCING_METHOD_RESPONSETIME: return responsetime(b, route, out, max, filter, arg); default: abort(); } } od_storage_balancing_t *od_balancing_get_effective(od_client_t *client, od_rule_storage_t *storage) { od_system_server_t *l = client->source; if (l != NULL && l->config->balancing_override_set) { return &l->config->balancing_override; } return &storage->balancing; } odyssey-1.6.0-rc1/sources/cancel.c000066400000000000000000000014711526064202200167560ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include int od_cancel(od_global_t *global, od_rule_storage_t *storage, const od_address_t *address, kiwi_key_t *key, od_id_t *server_id) { od_instance_t *instance = global->instance; od_log(&instance->logger, "cancel", NULL, NULL, "cancel for %s%.*s", server_id->id_prefix, (int)sizeof(server_id->id), server_id->id); od_server_t *server = od_server_allocate(0); server->global = global; od_backend_connect_cancel(server, storage, address, key); od_backend_close_connection(server); od_backend_close(server); return 0; } odyssey-1.6.0-rc1/sources/cfg/000077500000000000000000000000001526064202200161215ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/cfg/convert.c000066400000000000000000001456741526064202200177660ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PROMHTTP_FOUND #include #endif typedef struct { od_list_t spools; od_config_t *config; od_hba_rules_t *hba_rules; od_cfg_diag_list_t *diags; od_rules_t *rules; od_global_t *global; regex_t rfc952_hostname_regex; } convert_ctx_t; #define COPY_STR(field, out, diags) \ do { \ if ((field).seen.is_set) { \ out = od_strdup((field).value); \ if (out == NULL) { \ od_cfg_diag_error( \ diags, (field).seen.location, \ "can't allocate string value"); \ goto error; \ } \ } \ } while (0) #define COPY_INT(field, out) \ do { \ if ((field).seen.is_set) { \ out = (field).value; \ } \ } while (0) #define COPY_BOOL(field, out) \ do { \ if ((field).seen.is_set) { \ out = (field).value; \ } \ } while (0) #define COPY_U64(field, out) \ do { \ if ((field).seen.is_set) { \ out = (field).value; \ } \ } while (0) static int parse_tsa(const char *s, od_target_session_attrs_t *out) { if (strcmp(s, "read-write") == 0) { *out = OD_TARGET_SESSION_ATTRS_RW; return 0; } else if (strcmp(s, "any") == 0) { *out = OD_TARGET_SESSION_ATTRS_ANY; return 0; } else if (strcmp(s, "prefer-standby") == 0) { *out = OD_TARGET_SESSION_ATTRS_PREFER_STANDBY; return 0; } else if (strcmp(s, "read-only") == 0) { *out = OD_TARGET_SESSION_ATTRS_RO; return 0; } return 1; } #define COPY_TSA(field, out, diags) \ do { \ if ((field).seen.is_set) { \ int rc = parse_tsa((field).value, &out); \ if (rc) { \ od_cfg_diag_error( \ diags, (field).seen.location, \ "can't parse target session attrs from '%s'", \ (field).value); \ goto error; \ } \ } \ } while (0) static int convert_balancing(const od_cfg_balancing_t *cfg, od_storage_balancing_t *out, od_cfg_diag_list_t *diags) { od_storage_balancing_init(out); if (!cfg->seen.is_set) { return 0; } if (cfg->method.seen.is_set) { int mlen = (int)strlen(cfg->method.name); od_balancing_method_t method = od_balancing_method_from_str(cfg->method.name, mlen); if (method == OD_BALANCING_METHOD_UNDEF) { od_cfg_diag_error(diags, cfg->method.seen.location, "unexpected balancing method '%s'", cfg->method.name); return -1; } if (method != OD_BALANCING_METHOD_ROUNDROBIN && method != OD_BALANCING_METHOD_LEASTCONN) { od_cfg_diag_error( diags, cfg->method.seen.location, "not implemented balancing method '%s'", cfg->method.name); return -1; } out->method.type = method; COPY_BOOL(cfg->method.az_aware, out->method.az_aware); } COPY_BOOL(cfg->show_notice_messages, out->debug_notice); return 0; } static inline void ldap_not_supported(od_cfg_diag_list_t *diags, od_cfg_location_t location) __attribute__((unused)); static inline void ldap_not_supported(od_cfg_diag_list_t *diags, od_cfg_location_t location) { od_cfg_diag_error(diags, location, "LDAP is not supported by this build"); } #define CHECK_LDAP_NOT_SET(field, diags) \ do { \ if ((field).seen.is_set) { \ ldap_not_supported((diags), (field).seen.location); \ goto error; \ } \ } while (0) static od_rule_conn_type_t convert_conn_type(od_cfg_conn_type_t type) { switch (type) { case OD_CFG_CONN_TYPE_LOCAL: return OD_RULE_CONN_TYPE_LOCAL; case OD_CFG_CONN_TYPE_HOST: return OD_RULE_CONN_TYPE_HOST; case OD_CFG_CONN_TYPE_HOSTSSL: return OD_RULE_CONN_TYPE_HOSTSSL; case OD_CFG_CONN_TYPE_HOSTNOSSL: return OD_RULE_CONN_TYPE_HOSTNOSSL; case OD_CFG_CONN_TYPE_DEFAULT: default: return OD_RULE_CONN_TYPE_DEFAULT; } } static int parse_hba_names(const char *path, int line_no, const char *token, od_hba_rule_name_t *name, bool is_db, od_cfg_diag_list_t *diags, od_cfg_location_t loc) { /* token may be a comma-separated list: db1,db2 or a keyword */ char *copy = od_strdup(token); if (copy == NULL) { od_cfg_diag_error(diags, loc, "%s:%d: out of memory", path, line_no); return -1; } char *save = NULL; char *item = strtok_r(copy, ",", &save); while (item != NULL) { if (strcmp(item, "all") == 0) { name->flags |= OD_HBA_NAME_ALL; } else if (is_db && strcmp(item, "sameuser") == 0) { name->flags |= OD_HBA_NAME_SAMEUSER; } else { od_hba_rule_name_item_t *ni = od_hba_rule_name_item_add(name); if (ni == NULL) { od_free(copy); od_cfg_diag_error(diags, loc, "%s:%d: out of memory", path, line_no); return -1; } ni->value = od_strdup(item); if (ni->value == NULL) { od_free(copy); od_cfg_diag_error(diags, loc, "%s:%d: out of memory", path, line_no); return -1; } } item = strtok_r(NULL, ",", &save); } od_free(copy); return 0; } static int parse_hba_file(const char *path, od_hba_rules_t *hba_rules, od_cfg_diag_list_t *diags, od_cfg_location_t loc) { FILE *f = fopen(path, "r"); if (f == NULL) { od_cfg_diag_error(diags, loc, "can't open hba_file '%s': %s", path, strerror(errno)); return -1; } char line_buf[4096]; int line_no = 0; int rc = 0; while (fgets(line_buf, sizeof(line_buf), f) != NULL) { line_no++; /* strip trailing newline / comment */ char *comment = strchr(line_buf, '#'); if (comment) { *comment = '\0'; } /* tokenise by whitespace */ char *save = NULL; char *tok[6]; int ntok = 0; char *t = strtok_r(line_buf, " \t\r\n", &save); while (t != NULL && ntok < 6) { tok[ntok++] = t; t = strtok_r(NULL, " \t\r\n", &save); } if (ntok == 0) { continue; /* blank / comment line */ } od_cfg_location_t rule_loc = od_cfg_location_empty(path); rule_loc.first_line = line_no; rule_loc.last_line = line_no; od_hba_rule_t *rule = od_hba_rule_create(); if (rule == NULL) { od_cfg_diag_error(diags, rule_loc, "%s:%d: out of memory", path, line_no); rc = -1; break; } /* field 0: connection type */ const char *conn_str = tok[0]; if (strcmp(conn_str, "local") == 0) { rule->connection_type = OD_CONFIG_HBA_LOCAL; } else if (strcmp(conn_str, "host") == 0) { rule->connection_type = OD_CONFIG_HBA_HOST; } else if (strcmp(conn_str, "hostssl") == 0) { rule->connection_type = OD_CONFIG_HBA_HOSTSSL; } else if (strcmp(conn_str, "hostnossl") == 0) { rule->connection_type = OD_CONFIG_HBA_HOSTNOSSL; } else { od_cfg_diag_error(diags, rule_loc, "%s:%d: unknown connection type '%s'", path, line_no, conn_str); od_hba_rule_free(rule); rc = -1; break; } int is_local = (rule->connection_type == OD_CONFIG_HBA_LOCAL); /* local: conn db user auth => 4 tokens * host: conn db user addr auth => 5 tokens (CIDR) * conn db user addr mask auth => 6 tokens (separate mask) */ int min_tok = is_local ? 4 : 5; if (ntok < min_tok) { od_cfg_diag_error( diags, rule_loc, "%s:%d: too few fields (got %d, expected at least %d)", path, line_no, ntok, min_tok); od_hba_rule_free(rule); rc = -1; break; } /* field 1: database */ if (parse_hba_names(path, line_no, tok[1], &rule->database, true, diags, rule_loc) != 0) { od_hba_rule_free(rule); rc = -1; break; } /* field 2: user */ if (parse_hba_names(path, line_no, tok[2], &rule->user, false, diags, rule_loc) != 0) { od_hba_rule_free(rule); rc = -1; break; } const char *auth_str; if (!is_local) { /* field 3: address (possibly addr/prefix) */ char *addr_tok = od_strdup(tok[3]); if (addr_tok == NULL) { od_cfg_diag_error(diags, rule_loc, "%s:%d: out of memory", path, line_no); od_hba_rule_free(rule); rc = -1; break; } char *slash = strchr(addr_tok, '/'); if (slash) { /* CIDR notation: addr/prefix */ *slash++ = '\0'; if (od_address_read(&rule->address_range.addr, addr_tok) != 0) { od_cfg_diag_error( diags, rule_loc, "%s:%d: invalid IP address '%s'", path, line_no, addr_tok); od_free(addr_tok); od_hba_rule_free(rule); rc = -1; break; } if (od_address_range_read_prefix( &rule->address_range, slash) == -1) { od_cfg_diag_error( diags, rule_loc, "%s:%d: invalid prefix length '%s'", path, line_no, slash); od_free(addr_tok); od_hba_rule_free(rule); rc = -1; break; } od_free(addr_tok); /* field 4: auth method */ auth_str = tok[4]; } else { /* separate address + mask: field 3 = addr, field 4 = mask, field 5 = auth */ if (od_address_read(&rule->address_range.addr, addr_tok) != 0) { od_cfg_diag_error( diags, rule_loc, "%s:%d: invalid IP address '%s'", path, line_no, addr_tok); od_free(addr_tok); od_hba_rule_free(rule); rc = -1; break; } od_free(addr_tok); if (ntok < 6) { od_cfg_diag_error( diags, rule_loc, "%s:%d: expected network mask and auth method", path, line_no); od_hba_rule_free(rule); rc = -1; break; } if (od_address_read(&rule->address_range.mask, tok[4]) != 0) { od_cfg_diag_error( diags, rule_loc, "%s:%d: invalid network mask '%s'", path, line_no, tok[4]); od_hba_rule_free(rule); rc = -1; break; } auth_str = tok[5]; } } else { auth_str = tok[3]; } /* auth method */ if (strcmp(auth_str, "allow") == 0 || strcmp(auth_str, "trust") == 0) { rule->auth_method = OD_CONFIG_HBA_ALLOW; } else if (strcmp(auth_str, "deny") == 0 || strcmp(auth_str, "reject") == 0) { rule->auth_method = OD_CONFIG_HBA_DENY; } else { od_cfg_diag_error( diags, rule_loc, "%s:%d: unknown auth method '%s' " "(only allow/trust/deny/reject supported)", path, line_no, auth_str); od_hba_rule_free(rule); rc = -1; break; } od_hba_rules_add(hba_rules, rule); } fclose(f); return rc; } static int import_hba(const od_cfg_string_field_t *hba_file, od_config_t *config, od_hba_rules_t *hba_rules, od_cfg_diag_list_t *diags) { (void)config; return parse_hba_file(hba_file->value, hba_rules, diags, hba_file->seen.location); } int convert_global(const od_cfg_global_t *cfg, od_config_t *config, od_global_t *global, od_hba_rules_t *hba_rules, od_cfg_diag_list_t *diags) { COPY_BOOL(cfg->daemonize, config->daemonize); COPY_BOOL(cfg->sequential_routing, config->sequential_routing); COPY_BOOL(cfg->enable_online_restart, config->enable_online_restart_feature); COPY_BOOL(cfg->virtual_processing, config->virtual_processing); COPY_BOOL(cfg->virtual_transaction, config->virtual_transaction); COPY_BOOL(cfg->bindwith_reuseport, config->bindwith_reuseport); COPY_BOOL(cfg->enable_host_watcher, config->host_watcher_enabled); COPY_BOOL(cfg->log_debug, config->log_debug); COPY_BOOL(cfg->log_to_stdout, config->log_to_stdout); COPY_BOOL(cfg->log_config, config->log_config); COPY_BOOL(cfg->log_session, config->log_session); COPY_BOOL(cfg->log_query, config->log_query); COPY_BOOL(cfg->log_stats, config->log_stats); COPY_BOOL(cfg->log_async, config->log_async); COPY_BOOL(cfg->log_syslog, config->log_syslog); COPY_BOOL(cfg->smart_search_path_enquoting, config->smart_search_path_enquoting); COPY_BOOL(cfg->nodelay, config->nodelay); COPY_BOOL(cfg->disable_nolinger, config->disable_nolinger); COPY_BOOL(cfg->log_general_stats_prom, config->log_general_stats_prom); COPY_BOOL(cfg->log_route_stats_prom, config->log_route_stats_prom); COPY_INT(cfg->priority, config->priority); COPY_INT(cfg->graceful_shutdown_timeout_ms, config->graceful_shutdown_timeout_ms); COPY_INT(cfg->log_queue_depth, config->log_queue_depth); COPY_INT(cfg->stats_interval, config->stats_interval); COPY_INT(cfg->client_max, config->client_max); if (config->client_max > 0) { config->client_max_set = 1; } COPY_INT(cfg->client_max_routing, config->client_max_routing); COPY_INT(cfg->accept_rate_limit, config->accept_rate_limit); COPY_INT(cfg->server_login_retry, config->server_login_retry); COPY_INT(cfg->readahead, config->readahead); COPY_INT(cfg->keepalive, config->keepalive); COPY_INT(cfg->keepalive_keep_interval, config->keepalive_keep_interval); COPY_INT(cfg->keepalive_probes, config->keepalive_probes); COPY_INT(cfg->keepalive_usr_timeout, config->keepalive_usr_timeout); COPY_INT(cfg->max_sigterms_to_die, config->max_sigterms_to_die); COPY_INT(cfg->backend_connect_timeout_ms, config->backend_connect_timeout_ms); COPY_INT(cfg->cancel_timeout_ms, config->cancel_timeout_ms); COPY_INT(cfg->cancel_queue_timeout_ms, config->cancel_queue_timeout_ms); COPY_INT(cfg->cancel_max_inflight, config->cancel_max_inflight); COPY_INT(cfg->cancel_rate_limit, config->cancel_rate_limit); COPY_INT(cfg->resolvers, config->resolvers); COPY_INT(cfg->dns_cache_ttl, config->dns_ttl_ms); COPY_INT(cfg->cache_msg_gc_size, config->cache_msg_gc_size); COPY_INT(cfg->cache_msg_gc_count, config->cache_msg_gc_count); COPY_INT(cfg->cache_coroutine, config->cache_coroutine); COPY_INT(cfg->coroutine_stack_size, config->coroutine_stack_size); if (config->coroutine_stack_size < 16) { od_cfg_diag_warning( diags, cfg->coroutine_stack_size.seen.location, "coroutine_stack_size %d is below the recommended minimum of 16 pages (%d KB); " "values below 16 may cause stack overflows with TLS, LDAP, or config reload", config->coroutine_stack_size, config->coroutine_stack_size * 4); } COPY_INT(cfg->system_coroutine_stack_size, config->system_coroutine_stack_size); COPY_INT(cfg->group_checker_interval, config->group_checker_interval); COPY_INT(cfg->workers, config->workers); if (config->keepalive_usr_timeout < 0) { config->keepalive_usr_timeout = mm_io_advice_keepalive_usr_timeout( config->keepalive, config->keepalive_keep_interval, config->keepalive_probes); } if (!config->client_max_routing) { config->client_max_routing = config->workers * 64; } if (cfg->promhttp_server_port.seen.is_set) { #ifdef PROMHTTP_FOUND if (od_prom_set_port(cfg->promhttp_server_port.value, global->cron->metrics) != OK_RESPONSE) { od_cfg_diag_error( diags, cfg->promhttp_server_port.seen.location, "can't set prom http server port %d", cfg->promhttp_server_port.value); return -1; } #else (void)global; od_cfg_diag_error( diags, cfg->promhttp_server_port.seen.location, "can't set prom http server port: current build does not support PROMHTTP"); return -1; #endif } COPY_STR(cfg->pid_file, config->pid_file, diags); COPY_STR(cfg->unix_socket_dir, config->unix_socket_dir, diags); COPY_STR(cfg->unix_socket_mode, config->unix_socket_mode, diags); COPY_STR(cfg->locks_dir, config->locks_dir, diags); COPY_STR(cfg->external_auth_socket_path, config->external_auth_socket_path, diags); COPY_STR(cfg->log_file, config->log_file, diags); COPY_STR(cfg->log_format, config->log_format, diags); COPY_STR(cfg->log_syslog_ident, config->log_syslog_ident, diags); COPY_STR(cfg->log_syslog_facility, config->log_syslog_facility, diags); if (cfg->availability_zone.seen.is_set) { const char *val = cfg->availability_zone.value; size_t len = strlen(val); if (len > OD_MAX_AVAILABILITY_ZONE_LENGTH - 1) { od_cfg_diag_error( diags, cfg->availability_zone.seen.location, "availability_zone string is too large (%lu, but max is %u)", len, OD_MAX_AVAILABILITY_ZONE_LENGTH - 1); return -1; } strcpy(config->availability_zone, val); } if (cfg->cpu_affinity.seen.is_set) { config->cpu_affinity = od_malloc(sizeof(od_affinity_config_t)); if (config->cpu_affinity == NULL) { od_cfg_diag_error(diags, cfg->cpu_affinity.seen.location, "can't allocate cpu_affinity"); return -1; } od_affinity_config_init(config->cpu_affinity); char buf[256]; int rc = od_affinity_config_parse( cfg->cpu_affinity.value, strlen(cfg->cpu_affinity.value), config->cpu_affinity, buf, sizeof(buf)); if (rc != 0) { od_cfg_diag_error(diags, cfg->cpu_affinity.seen.location, "can't parse cpu_affinity: %s", buf); return -1; } } if (cfg->hba_file.seen.is_set) { COPY_STR(cfg->hba_file, config->hba_file, diags); int rc = import_hba(&cfg->hba_file, config, hba_rules, diags); if (rc != 0) { return rc; } } if (cfg->cancel_max_inflight.seen.is_set && cfg->cancel_rate_limit.seen.is_set) { od_cfg_diag_error( diags, cfg->cancel_rate_limit.seen.location, "can't set cancel_max_inflight and cancel_rate_limit simultaneously"); return -1; } return 0; error: return -1; } static int parse_quantiles(od_cfg_diag_list_t *diags, const od_cfg_string_field_t *field, double **quantiles, int *count) { int comma_cnt = 1; const char *c = field->value; while (*c) { if (*c == ',') { comma_cnt++; } c++; } *quantiles = od_malloc(sizeof(double) * comma_cnt); if (*quantiles == NULL) { od_cfg_diag_error(diags, field->seen.location, "can't allocate quantiles"); return -1; } double *array = *quantiles; *count = 0; c = field->value; while (*c) { int length = sscanf(c, "%lf", array + *count); if (length != 1 || array[*count] > 1 || array[*count] < 0) { od_cfg_diag_error(diags, field->seen.location, "can't parse quantiles from '%s'", field->value); od_free(*quantiles); return -1; } *count += 1; while (*c != '\0' && *c != ',') { c++; } if (*c == '\0') { break; } c++; } return 0; } static int convert_rule_settings(const od_cfg_route_t *cfg, od_list_t *spools, od_rules_t *rules, od_rule_t *rule, od_cfg_diag_list_t *diags) { (void)rules; #ifdef LDAP_FOUND od_ldap_storage_credentials_t *lsc = NULL; #endif COPY_STR(cfg->authentication, rule->auth, diags); for (size_t i = 0; i < cfg->auth_common_names_count; ++i) { od_cfg_rule_auth_common_name_t *name = cfg->auth_common_names[i]; if (name->is_default) { rule->auth_common_name_default = 1; continue; } od_rule_auth_t *auth = od_rules_auth_add(rule); if (auth == NULL) { od_cfg_diag_error(diags, name->location, "can't allocate rule auth"); goto error; } auth->common_name = od_strdup(name->name); if (auth->common_name == NULL) { od_cfg_diag_error(diags, name->location, "can't allocate name"); goto error; } } COPY_STR(cfg->auth_module, rule->auth_module, diags); COPY_BOOL(cfg->enable_mdb_iamproxy_auth, rule->enable_mdb_iamproxy_auth); COPY_STR(cfg->mdb_iamproxy_socket_path, rule->mdb_iamproxy_socket_path, diags); #ifdef PAM_FOUND COPY_STR(cfg->auth_pam_service, rule->auth_pam_service, diags); #else if (cfg->auth_pam_service.seen.is_set) { od_cfg_diag_error(diags, cfg->auth_pam_service.seen.location, "PAM is not supported by this build"); goto error; } #endif COPY_TSA(cfg->target_session_attrs, rule->target_session_attrs, diags); COPY_STR(cfg->auth_query, rule->auth_query, diags); COPY_STR(cfg->auth_query_db, rule->auth_query_db, diags); COPY_STR(cfg->auth_query_user, rule->auth_query_user, diags); COPY_BOOL(cfg->password_passthrough, rule->enable_password_passthrough); COPY_STR(cfg->password, rule->password, diags); if (rule->password != NULL) { rule->password_len = strlen(rule->password); } if (cfg->role.seen.is_set) { if (strcmp(cfg->role.value, "admin") == 0) { rule->user_role = OD_RULE_ROLE_ADMIN; } else if (strcmp(cfg->role.value, "stat") == 0) { rule->user_role = OD_RULE_ROLE_STAT; } else if (strcmp(cfg->role.value, "notallow") == 0) { rule->user_role = OD_RULE_ROLE_NOTALLOW; } else { od_cfg_diag_error(diags, cfg->role.seen.location, "role type '%s' is unknown", cfg->role.value); goto error; } } COPY_INT(cfg->client_max, rule->client_max); if (rule->client_max > 0) { rule->client_max_set = 1; } COPY_BOOL(cfg->client_fwd_error, rule->client_fwd_error); COPY_BOOL(cfg->client_show_id, rule->client_show_id); COPY_BOOL(cfg->reserve_session_server_connection, rule->reserve_session_server_connection); if (cfg->quantiles.seen.is_set) { int rc = parse_quantiles(diags, &cfg->quantiles, &rule->quantiles, &rule->quantiles_count); if (rc != 0) { goto error; } } COPY_BOOL(cfg->application_name_add_host, rule->application_name_add_host); COPY_BOOL(cfg->server_drop_on_backend_plan_error, rule->server_drop_on_cached_plan_error); COPY_BOOL(cfg->server_drop_on_oom, rule->server_drop_on_oom); if (cfg->server_lifetime.seen.is_set) { rule->server_lifetime_us = cfg->server_lifetime.value * 1000000L; } /* ignore ldap_pool_* */ COPY_BOOL(cfg->maintain_params, rule->maintain_params); COPY_STR(cfg->pool, rule->pool->pool_type_str, diags); COPY_STR(cfg->pool_routing, rule->pool->routing_type, diags); COPY_INT(cfg->min_pool_size, rule->pool->min_size); COPY_INT(cfg->pool_size, rule->pool->size); COPY_INT(cfg->pool_timeout, rule->pool->timeout); COPY_INT(cfg->pool_ttl, rule->pool->ttl); COPY_BOOL(cfg->pool_discard, rule->pool->discard); COPY_BOOL(cfg->pool_smart_discard, rule->pool->smart_discard); COPY_STR(cfg->pool_discard_query, rule->pool->discard_query, diags); COPY_BOOL(cfg->pool_cancel, rule->pool->cancel); COPY_BOOL(cfg->pool_rollback, rule->pool->rollback); COPY_U64(cfg->pool_reset_timeout_ms, rule->pool->reset_timeout_ms); COPY_BOOL(cfg->pool_reserve_prepared_statement, rule->pool->reserve_prepared_statement); COPY_INT(cfg->server_pstmt_cache_size, rule->pool->server_pstmt_cache_size); COPY_BOOL(cfg->pool_pin_on_listen, rule->pool->pin_on_listen); COPY_BOOL(cfg->pool_attach_check, rule->pool->attach_check); COPY_BOOL(cfg->pool_acquire_fail_fast, rule->pool->acquire_fail_fast); if (cfg->pool_notice_after_waiting_ms.seen.is_set) { if (cfg->pool_notice_after_waiting_ms.value > (uint64_t)INT_MAX) { od_cfg_diag_error( diags, cfg->pool_notice_after_waiting_ms.seen.location, "pool_notice_after_waiting_ms value is too large"); goto error; } rule->pool->notice_after_waiting_ms = (int)cfg->pool_notice_after_waiting_ms.value; } if (cfg->pool_client_idle_timeout.seen.is_set) { rule->pool->client_idle_timeout = cfg->pool_client_idle_timeout.value * interval_usec; } if (cfg->pool_idle_in_transaction_timeout.seen.is_set) { rule->pool->idle_in_transaction_timeout = cfg->pool_idle_in_transaction_timeout.value * interval_usec; } if (cfg->shared_pool.seen.is_set) { od_list_t *i; od_list_foreach (spools, i) { od_shared_pool_t *sp = od_container_of(i, od_shared_pool_t, link); if (strcmp(cfg->shared_pool.value, sp->name) == 0) { rule->shared_pool = od_shared_pool_ref(sp); break; } } if (rule->shared_pool == NULL) { od_cfg_diag_error(diags, cfg->shared_pool.seen.location, "unknown shared_pool \"%s\"", cfg->shared_pool.value); goto error; } } COPY_STR(cfg->storage, rule->storage_name, diags); COPY_STR(cfg->storage_database, rule->storage_db, diags); COPY_STR(cfg->storage_user, rule->storage_user, diags); if (rule->storage_user != NULL) { rule->storage_user_len = strlen(rule->storage_user); } COPY_STR(cfg->storage_password, rule->storage_password, diags); if (rule->storage_password != NULL) { rule->storage_password_len = strlen(rule->storage_password); } COPY_BOOL(cfg->log_debug, rule->log_debug); COPY_BOOL(cfg->log_query, rule->log_query); COPY_INT(cfg->keepalive, rule->keepalive); COPY_INT(cfg->keepalive_keep_interval, rule->keepalive_keep_interval); COPY_INT(cfg->keepalive_probes, rule->keepalive_probes); COPY_INT(cfg->keepalive_usr_timeout, rule->keepalive_usr_timeout); #ifdef LDAP_FOUND COPY_STR(cfg->ldap_endpoint_name, rule->ldap_endpoint_name, diags); if (cfg->ldap_endpoint_name.seen.is_set) { od_ldap_endpoint_t *le = od_ldap_endpoint_find( &rules->ldap_endpoints, cfg->ldap_endpoint_name.value); if (le == NULL) { od_cfg_diag_error(diags, cfg->ldap_endpoint_name.seen.location, "LDAP endpoint \"%s\" is unknown", cfg->ldap_endpoint_name.value); goto error; } rule->ldap_endpoint = od_ldap_endpoint_ref(le); } COPY_STR(cfg->ldap_storage_credentials_attr, rule->ldap_storage_credentials_attr, diags); #else CHECK_LDAP_NOT_SET(cfg->ldap_endpoint_name, diags); CHECK_LDAP_NOT_SET(cfg->ldap_endpoint_name, diags); CHECK_LDAP_NOT_SET(cfg->ldap_storage_credentials_attr, diags); #endif for (size_t i = 0; i < cfg->ldap_storage_credentials_count; ++i) { od_cfg_ldap_storage_credentials_t *c = cfg->ldap_storage_credentials[i]; #ifdef LDAP_FOUND lsc = od_ldap_storage_credentials_alloc(); if (lsc == NULL) { od_cfg_diag_error(diags, c->location, "can't allocate LDAP storage cred"); goto error; } lsc->name = od_strdup(c->name); if (lsc->name == NULL) { od_cfg_diag_error( diags, c->location, "can't allocate LDAP storage cred name"); goto error; } if (strlen(lsc->name) == 0) { od_cfg_diag_error( diags, c->location, "empty LDAP storage credentials definition"); goto error; } if (od_ldap_storage_credentials_find( &rule->ldap_storage_creds_list, lsc->name) != NULL) { od_cfg_diag_error( diags, c->location, "redefinition of LDAP storage creds \"%s\"", lsc->name); goto error; } COPY_STR(c->ldap_storage_username, lsc->lsc_username, diags); COPY_STR(c->ldap_storage_password, lsc->lsc_password, diags); if (lsc->lsc_username == NULL || strlen(lsc->lsc_username) == 0) { od_cfg_diag_error( diags, c->location, "username in ldap_storage_credentials '%s' is not defined or is empty", lsc->name); goto error; } if (lsc->lsc_password == NULL || strlen(lsc->lsc_password) == 0) { od_cfg_diag_error( diags, c->location, "password in ldap_storage_credentials '%s' is not defined or is empty", lsc->name); goto error; } od_rule_ldap_storage_credentials_add(rule, lsc); od_ldap_storage_credentials_free(lsc); lsc = NULL; #else ldap_not_supported(diags, c->location); goto error; #endif } #ifdef LDAP_FOUND if (rule->ldap_storage_credentials_attr != NULL) { if (rule->ldap_endpoint == NULL) { od_cfg_diag_error( diags, cfg->ldap_storage_credentials_attr.seen.location, "ldap endpoint is not set, but storage cred attrs is"); goto error; } } #endif COPY_INT(cfg->catchup_timeout, rule->catchup_timeout); if (rule->catchup_timeout != 0) { /* TODO: remove catchup_checks */ rule->catchup_checks = 1; } for (size_t i = 0; i < cfg->pgoptions.pairs_count; ++i) { od_cfg_string_kvp_t *kvp = cfg->pgoptions.pairs[i]; kiwi_vars_update(&rule->vars, kvp->key, strlen(kvp->key) + 1, kvp->value, strlen(kvp->value) + 1); } if (cfg->backend_startup_options.pairs_count > 0) { rule->backend_startup_vars_sz = cfg->backend_startup_options.pairs_count; rule->backend_startup_vars = od_calloc(rule->backend_startup_vars_sz, sizeof(untyped_kiwi_var_t)); if (rule->backend_startup_vars == NULL) { od_cfg_diag_error( diags, cfg->location, "can't allocate backend startup options"); goto error; } for (size_t i = 0; i < cfg->backend_startup_options.pairs_count; ++i) { untyped_kiwi_var_t *ptr = &rule->backend_startup_vars[i]; ptr->name = od_strdup( cfg->backend_startup_options.pairs[i]->key); if (ptr->name == NULL) { od_cfg_diag_error( diags, cfg->location, "can't allocate backend startup options"); goto error; } ptr->name_len = strlen(ptr->name); ptr->value = od_strdup( cfg->backend_startup_options.pairs[i]->value); if (ptr->value == NULL) { od_cfg_diag_error( diags, cfg->location, "can't allocate backend startup options"); goto error; } ptr->value_len = strlen(ptr->value); } } if (cfg->shared_pool.seen.is_set && cfg->pool_size.seen.is_set) { od_cfg_diag_error( diags, cfg->location, "shared_pool and pool_size can't be set simultaneously"); goto error; } return 0; error: #ifdef LDAP_FOUND if (lsc != NULL) { od_ldap_storage_credentials_free(lsc); } #endif return -1; } static int convert_shared_pool(convert_ctx_t *ctx, const od_cfg_shared_pool_t *cfg) { od_list_t *pools = &ctx->spools; od_cfg_diag_list_t *diags = ctx->diags; od_list_t *i; od_list_foreach (pools, i) { od_shared_pool_t *s = od_container_of(i, od_shared_pool_t, link); if (strcmp(s->name, cfg->name) == 0) { od_cfg_diag_error(diags, cfg->name_location, "redefinition of shared pool \"%s\"", cfg->name); return -1; } } od_shared_pool_t *sp = od_shared_pool_create(cfg->name); if (sp == NULL) { od_cfg_diag_error(diags, cfg->location, "can't allocate shared pool"); return -1; } COPY_INT(cfg->pool_size, sp->pool_size); od_list_append(pools, &sp->link); return 0; } static int convert_shared_pools(convert_ctx_t *ctx, const od_cfg_model_t *cfg) { for (size_t i = 0; i < cfg->shared_pools_count; ++i) { const od_cfg_shared_pool_t *sp = cfg->shared_pools[i]; int rc = convert_shared_pool(ctx, sp); if (rc != 0) { return rc; } } return 0; } static int convert_listen(convert_ctx_t *ctx, const od_cfg_listen_t *cfg) { od_config_t *config = ctx->config; od_cfg_diag_list_t *diags = ctx->diags; od_config_listen_t *listen = od_config_listen_add(config); if (listen == NULL) { od_cfg_diag_error(diags, cfg->location, "can't allocate listen"); return -1; } COPY_STR(cfg->host, listen->host, diags); COPY_INT(cfg->port, listen->port); COPY_TSA(cfg->target_session_attrs, listen->target_session_attrs, diags); { int rc = convert_balancing(&cfg->balancing, &listen->balancing_override, diags); if (rc != 0) { goto error; } listen->balancing_override_set = cfg->balancing.seen.is_set; } COPY_INT(cfg->client_login_timeout, listen->client_login_timeout); COPY_INT(cfg->catchup_timeout, listen->catchup_timeout); COPY_INT(cfg->reserved_clients, listen->reserved_clients); COPY_INT(cfg->backlog, listen->backlog); COPY_STR(cfg->tls, listen->tls_opts->tls, diags); COPY_STR(cfg->tls_ca_file, listen->tls_opts->tls_ca_file, diags); COPY_STR(cfg->tls_key_file, listen->tls_opts->tls_key_file, diags); COPY_STR(cfg->tls_cert_file, listen->tls_opts->tls_cert_file, diags); COPY_STR(cfg->tls_protocols, listen->tls_opts->tls_protocols, diags); /* no compression field */ listen->storage_count = cfg->storages_count; if (listen->storage_count > 0) { listen->storage_names = od_malloc(sizeof(char *) * listen->storage_count); if (listen->storage_names == NULL) { od_cfg_diag_error(diags, cfg->location, "can't allocate storage names"); goto error; } memset(listen->storage_names, 0, sizeof(char *) * listen->storage_count); for (size_t i = 0; i < listen->storage_count; ++i) { /* validate that the storage exists */ int found = 0; od_list_t *li; od_list_foreach (&ctx->rules->storages, li) { od_rule_storage_t *storage = od_container_of( li, od_rule_storage_t, link); if (strcmp(storage->name, cfg->storages[i]->name) == 0) { found = 1; break; } } if (!found) { for (size_t j = 0; j < i; ++j) { od_free(listen->storage_names[j]); } od_free(listen->storage_names); listen->storage_names = NULL; listen->storage_count = 0; od_cfg_diag_error(diags, cfg->location, "unknown storage '%s'", cfg->storages[i]->name); goto error; } listen->storage_names[i] = od_strdup(cfg->storages[i]->name); if (listen->storage_names[i] == NULL) { for (size_t j = 0; j < i; ++j) { od_free(listen->storage_names[j]); } od_free(listen->storage_names); listen->storage_names = NULL; listen->storage_count = 0; od_cfg_diag_error( diags, cfg->location, "can't allocate storage name"); goto error; } } } return 0; error: return -1; } static int convert_listens(convert_ctx_t *ctx, const od_cfg_model_t *model) { if (model->listens_count == 0) { od_cfg_diag_error(ctx->diags, model->location, "no listen sections defined"); return -1; } for (size_t i = 0; i < model->listens_count; ++i) { od_cfg_listen_t *l = model->listens[i]; int rc = convert_listen(ctx, l); if (rc != 0) { return rc; } } return 0; } static int convert_storage(const od_cfg_storage_t *cfg, od_list_t *spools, od_rules_t *rules, od_global_t *global, od_cfg_diag_list_t *diags) { od_rule_storage_t *storage = od_rules_storage_allocate(); if (storage == NULL) { od_cfg_diag_error(diags, cfg->location, "can't allocate storage"); return -1; } if (od_rules_storage_match(rules, cfg->name) != NULL) { od_cfg_diag_error(diags, cfg->location, "duplicate storage '%s' definition", cfg->name); goto error; } storage->name = od_strdup(cfg->name); if (storage->name == NULL) { od_cfg_diag_error(diags, cfg->name_location, "can't allocate name"); goto error; } od_rules_storage_add(rules, storage); COPY_STR(cfg->type, storage->type, diags); COPY_STR(cfg->host, storage->host, diags); COPY_INT(cfg->port, storage->port); COPY_STR(cfg->tls, storage->tls_opts->tls, diags); COPY_STR(cfg->tls_ca_file, storage->tls_opts->tls_ca_file, diags); COPY_STR(cfg->tls_key_file, storage->tls_opts->tls_key_file, diags); COPY_STR(cfg->tls_cert_file, storage->tls_opts->tls_cert_file, diags); COPY_STR(cfg->tls_protocols, storage->tls_opts->tls_protocols, diags); COPY_INT(cfg->server_max_routing, storage->server_max_routing); COPY_INT(cfg->endpoints_status_poll_interval_ms, storage->endpoints_status_poll_interval_ms); if (cfg->host.seen.is_set) { int rc = od_storage_parse_endpoints(cfg->host.value, &storage->endpoints, &storage->endpoints_count); if (rc != 0) { od_cfg_diag_error(diags, cfg->host.seen.location, "can't parse endpoints from '%s'", cfg->host.value); goto error; } for (size_t i = 0; i < storage->endpoints_count; ++i) { storage->endpoints[i].storage = storage; od_storage_endpoint_status_init( &storage->endpoints[i].status); } } { int rc = convert_balancing(&cfg->balancing, &storage->balancing, diags); if (rc != 0) { goto error; } } if (cfg->watchdog != NULL) { storage->watchdog = od_storage_watchdog_allocate(global); if (storage->watchdog == NULL) { od_cfg_diag_error(diags, cfg->watchdog->location, "can't allocate watchdog"); goto error; } od_storage_watchdog_t *watchdog = storage->watchdog; size_t route_name_sz = sizeof("watchdog_int_") + strlen(cfg->name); watchdog->route_usr = od_malloc(route_name_sz); if (watchdog->route_usr == NULL) { od_cfg_diag_error(diags, cfg->watchdog->location, "can't allocate watchdog route"); goto error; } snprintf(watchdog->route_usr, route_name_sz, "watchdog_int_%s", cfg->name); watchdog->route_db = od_strdup(watchdog->route_usr); if (watchdog->route_db == NULL) { od_cfg_diag_error(diags, cfg->watchdog->location, "can't allocate watchdog route"); goto error; } od_rule_t *rule; od_address_range_t address_range = od_address_range_create_default(); rule = od_rules_add_new_rule( rules, watchdog->route_db, 0 /* db_is_default */, watchdog->route_usr, 0 /* user_is_default */, &address_range, OD_RULE_CONN_TYPE_DEFAULT, 1 /* pool_internal */); od_address_range_destroy(&address_range); if (rule == NULL) { od_cfg_diag_error(diags, cfg->watchdog->location, "route '%s.%s': is redefined", watchdog->route_db, watchdog->route_usr); goto error; } int rc = convert_rule_settings(cfg->watchdog, spools, rules, rule, diags); if (rc != 0) { goto error; } COPY_STR(cfg->watchdog->watchdog_lag_query, watchdog->query, diags); COPY_INT(cfg->watchdog->watchdog_lag_interval, watchdog->interval); watchdog->storage_db = rule->storage_db; watchdog->storage_user = rule->storage_user; rule->pool->routing = OD_RULE_POOL_INTERNAL; watchdog->storage = storage; } return 0; error: od_rules_storage_unref(storage); return -1; } static int convert_storages(const od_cfg_model_t *model, od_list_t *spools, od_rules_t *rules, od_global_t *global, od_cfg_diag_list_t *diags) { if (model->storages_count == 0) { od_cfg_diag_error(diags, model->location, "no storage sections defined"); return -1; } for (size_t i = 0; i < model->storages_count; ++i) { od_cfg_storage_t *cfg = model->storages[i]; int rc = convert_storage(cfg, spools, rules, global, diags); if (rc != 0) { return rc; } } return 0; } static int convert_conn_drop_opts(const od_cfg_conn_drop_options_t *cfg, od_config_t *config) { if (!cfg->seen.is_set) { return 0; } od_config_conn_drop_options_t *opts = &config->conn_drop_options; COPY_BOOL(cfg->drop_enabled, opts->drop_enabled); COPY_INT(cfg->rate, opts->rate); COPY_INT(cfg->interval_ms, opts->interval_ms); return 0; } struct sig_name_num { const char *name; int num; }; static int parse_signal(const char *name) { static const struct sig_name_num sigs[] = { #ifdef SIGHUP { "SIGHUP", SIGHUP }, #endif #ifdef SIGINT { "SIGINT", SIGINT }, #endif #ifdef SIGQUIT { "SIGQUIT", SIGQUIT }, #endif #ifdef SIGABRT { "SIGABRT", SIGABRT }, #endif #ifdef SIGKILL { "SIGKILL", SIGKILL }, #endif #ifdef SIGUSR1 { "SIGUSR1", SIGUSR1 }, #endif #ifdef SIGUSR2 { "SIGUSR2", SIGUSR2 }, #endif #ifdef SIGTERM { "SIGTERM", SIGTERM }, #endif #ifdef SIGSTOP { "SIGSTOP", SIGSTOP }, #endif #ifdef SIGTSTP { "SIGTSTP", SIGTSTP }, #endif }; for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); ++i) { if (strcasecmp(sigs[i].name, name) == 0) { return sigs[i].num; } if (strncasecmp(name, "SIG", 3) != 0) { if (strcasecmp(sigs[i].name + 3, name) == 0) { return sigs[i].num; } } } return -1; } static int convert_soft_oom(convert_ctx_t *ctx, const od_cfg_soft_oom_t *cfg) { od_config_t *config = ctx->config; od_cfg_diag_list_t *diags = ctx->diags; od_config_soft_oom_t *soft_oom = &config->soft_oom; if (!cfg->seen.is_set) { return 0; } if (!cfg->limit.seen.is_set) { od_cfg_diag_error(diags, cfg->location, "limits is not set in soft_oom"); goto error; } COPY_INT(cfg->limit, soft_oom->limit_bytes); COPY_INT(cfg->check_interval_ms, soft_oom->check_interval_ms); soft_oom->enabled = 1; if (cfg->drop.seen.is_set) { soft_oom->drop.enabled = 1; COPY_INT(cfg->drop.max_rate, soft_oom->drop.max_rate); if (cfg->drop.signal.seen.is_set) { soft_oom->drop.signal = parse_signal(cfg->drop.signal.value); if (soft_oom->drop.signal == -1) { od_cfg_diag_error( diags, cfg->drop.signal.seen.location, "can't parse signal from '%s'", cfg->drop.signal.value); goto error; } } } if (cfg->process.seen.is_set) { size_t l = strlen(cfg->process.value); if (l >= sizeof(soft_oom->process)) { od_cfg_diag_error(diags, cfg->process.seen.location, "too long process name, max is %lu", sizeof(soft_oom->process)); goto error; } strcpy(soft_oom->process, cfg->process.value); } return 0; error: return -1; } static int convert_query_parsing(convert_ctx_t *ctx, const od_cfg_query_parsing_t *cfg) { if (!cfg->seen.is_set) { return 0; } od_config_t *config = ctx->config; od_cfg_diag_list_t *diags = ctx->diags; od_config_query_parsing_t *qp = &config->query_parsing; COPY_U64(cfg->mem_limit, qp->mem_limit_bytes); if (cfg->mode.seen.is_set) { switch (cfg->mode.value) { case OD_CFG_QUERY_PARSING_MODE_DISABLED: qp->mode = OD_CONFIG_QUERY_PARSING_MODE_DISABLED; break; case OD_CFG_QUERY_PARSING_MODE_MINIMAL: qp->mode = OD_CONFIG_QUERY_PARSING_MODE_MINIMAL; break; case OD_CFG_QUERY_PARSING_MODE_FULL: od_cfg_diag_error(diags, cfg->mode.seen.location, "full mode is not supported now"); return -1; default: od_assert(0); } } return 0; } static int parse_address_range(const od_cfg_route_t *rcfg, const regex_t *hostname_re, od_address_range_t *out, od_cfg_diag_list_t *diags) { *out = od_address_range_create_default(); if (rcfg->address_range == NULL) { return 0; } /* free the default "all" string allocated by od_address_range_create_default() */ od_free(out->string_value); out->string_value = NULL; /* string_value keeps the original string for display / copy purposes */ out->string_value = od_strdup(rcfg->address_range); if (out->string_value == NULL) { od_cfg_diag_error(diags, rcfg->address_range_location, "can't allocate address string"); return -1; } out->string_value_len = strlen(out->string_value); out->is_default = 0; out->is_hostname = 0; /* working copy for parsing — we may insert '\0' at the '/' */ char *addr_str = od_strdup(rcfg->address_range); if (addr_str == NULL) { od_cfg_diag_error(diags, rcfg->address_range_location, "can't allocate address string"); od_free(out->string_value); out->string_value = NULL; return -1; } char *mask_str = strchr(addr_str, '/'); if (mask_str) { *mask_str++ = 0; } if (od_address_read(&out->addr, addr_str) != 0) { /* not an IP address — try hostname */ int reti = regexec(hostname_re, addr_str, 0, NULL, 0); if (reti == 0) { out->is_hostname = 1; } else { od_cfg_diag_error(diags, rcfg->address_range_location, "invalid address or hostname: '%s'", rcfg->address_range); od_free(addr_str); od_free(out->string_value); out->string_value = NULL; return -1; } } else if (mask_str) { if (od_address_range_read_prefix(out, mask_str) == -1) { od_cfg_diag_error( diags, rcfg->address_range_location, "invalid network prefix length in '%s'", rcfg->address_range); od_free(addr_str); od_free(out->string_value); out->string_value = NULL; return -1; } } else { od_cfg_diag_error(diags, rcfg->address_range_location, "expected network mask in '%s'", rcfg->address_range); od_free(addr_str); od_free(out->string_value); out->string_value = NULL; return -1; } od_free(addr_str); return 0; } static int convert_user_route(const od_cfg_database_t *db, const od_cfg_route_t *rcfg, od_list_t *spools, od_rules_t *rules, const regex_t *hostname_re, od_cfg_diag_list_t *diags) { od_address_range_t address_range; int rc = parse_address_range(rcfg, hostname_re, &address_range, diags); if (rc != 0) { return -1; } od_rule_conn_type_t conn_type = convert_conn_type(rcfg->conn_type); od_rule_t *rule = od_rules_add_new_rule(rules, db->name, db->is_default, rcfg->name, rcfg->is_default, &address_range, conn_type, 0 /* pool_internal */); od_address_range_destroy(&address_range); if (rule == NULL) { od_cfg_diag_error(diags, rcfg->location, "route '%s.%s': is redefined", db->name, rcfg->name); return -1; } return convert_rule_settings(rcfg, spools, rules, rule, diags); } static int convert_group_route(const od_cfg_database_t *db, const od_cfg_route_t *gcfg, od_list_t *spools, od_rules_t *rules, od_global_t *global, const regex_t *hostname_re, od_cfg_diag_list_t *diags) { char route_usr[sizeof("group_") + strlen(gcfg->name)]; snprintf(route_usr, sizeof(route_usr), "group_%s", gcfg->name); od_address_range_t address_range; int rc = parse_address_range(gcfg, hostname_re, &address_range, diags); if (rc != 0) { return -1; } od_rule_conn_type_t conn_type = convert_conn_type(gcfg->conn_type); od_rule_t *rule = od_rules_add_new_rule(rules, db->name, db->is_default, route_usr, 0 /* user_is_default */, &address_range, conn_type, 0 /* pool_internal */); od_address_range_destroy(&address_range); if (rule == NULL) { od_cfg_diag_error(diags, gcfg->location, "route '%s.%s': is redefined", db->name, route_usr); return -1; } od_group_t *group = od_rules_group_allocate(global); if (group == NULL) { od_cfg_diag_error(diags, gcfg->location, "can't allocate group for '%s.%s'", db->name, gcfg->name); return -1; } group->group_name = od_strdup(gcfg->name); group->route_usr = od_strdup(rule->user_name); group->route_db = od_strdup(rule->db_name); if (group->group_name == NULL || group->route_usr == NULL || group->route_db == NULL) { od_cfg_diag_error(diags, gcfg->location, "can't allocate group strings for '%s.%s'", db->name, gcfg->name); od_group_free(group); return -1; } rule->group = group; rule->users_in_group = 0; rule->user_names = NULL; rc = convert_rule_settings(gcfg, spools, rules, rule, diags); if (rc != 0) { return -1; } /* group-specific fields go into od_group_t, not od_rule_t */ if (gcfg->group_query.seen.is_set) { group->group_query = od_strdup(gcfg->group_query.value); if (group->group_query == NULL) { od_cfg_diag_error(diags, gcfg->group_query.seen.location, "can't allocate group_query"); return -1; } } if (gcfg->group_query_user.seen.is_set) { group->group_query_user = od_strdup(gcfg->group_query_user.value); if (group->group_query_user == NULL) { od_cfg_diag_error(diags, gcfg->group_query_user.seen.location, "can't allocate group_query_user"); return -1; } } if (gcfg->group_query_db.seen.is_set) { group->group_query_db = od_strdup(gcfg->group_query_db.value); if (group->group_query_db == NULL) { od_cfg_diag_error(diags, gcfg->group_query_db.seen.location, "can't allocate group_query_db"); return -1; } } return 0; } static int convert_db(convert_ctx_t *ctx, const od_cfg_database_t *db) { for (size_t i = 0; i < db->users_count; ++i) { const od_cfg_route_t *rcfg = db->users[i]; int rc = convert_user_route(db, rcfg, &ctx->spools, ctx->rules, &ctx->rfc952_hostname_regex, ctx->diags); if (rc != 0) { return -1; } } for (size_t i = 0; i < db->groups_count; ++i) { const od_cfg_route_t *gcfg = db->groups[i]; int rc = convert_group_route(db, gcfg, &ctx->spools, ctx->rules, ctx->global, &ctx->rfc952_hostname_regex, ctx->diags); if (rc != 0) { return -1; } } return 0; } #ifdef LDAP_FOUND static int convert_ldap_endpoints(convert_ctx_t *ctx, const od_cfg_model_t *model) { for (size_t i = 0; i < model->ldap_endpoints_count; ++i) { const od_cfg_ldap_endpoint_t *cfg = model->ldap_endpoints[i]; od_ldap_endpoint_t *le = od_ldap_endpoint_alloc(); if (le == NULL) { od_cfg_diag_error(ctx->diags, cfg->location, "can't allocate ldap_endpoint"); return -1; } le->name = od_strdup(cfg->name); if (le->name == NULL) { od_ldap_endpoint_free(le); od_cfg_diag_error(ctx->diags, cfg->location, "can't allocate ldap_endpoint name"); return -1; } #define COPY_LDAP_STR(field) \ do { \ if ((cfg->field).seen.is_set) { \ le->field = od_strdup((cfg->field).value); \ if (le->field == NULL) { \ od_ldap_endpoint_free(le); \ od_cfg_diag_error( \ ctx->diags, \ (cfg->field).seen.location, \ "can't allocate ldap_endpoint field"); \ return -1; \ } \ } \ } while (0) COPY_LDAP_STR(ldapserver); COPY_LDAP_STR(ldapscheme); COPY_LDAP_STR(ldapprefix); COPY_LDAP_STR(ldapsuffix); COPY_LDAP_STR(ldapsearchattribute); COPY_LDAP_STR(ldapscope); COPY_LDAP_STR(ldapbasedn); COPY_LDAP_STR(ldapbinddn); COPY_LDAP_STR(ldapbindpasswd); COPY_LDAP_STR(ldapsearchfilter); #undef COPY_LDAP_STR if (cfg->ldapport.seen.is_set) { le->ldapport = cfg->ldapport.value; } if (od_ldap_endpoint_prepare(le) != OK_RESPONSE) { od_cfg_diag_error( ctx->diags, cfg->location, "can't prepare ldap_endpoint \"%s\" (missing ldapserver?)", cfg->name); od_ldap_endpoint_free(le); return -1; } od_rules_ldap_endpoint_add(ctx->rules, le); } return 0; } #endif static int convert_rules(convert_ctx_t *ctx, const od_cfg_model_t *cfg) { for (size_t i = 0; i < cfg->databases_count; ++i) { od_cfg_database_t *db = cfg->databases[i]; int rc = convert_db(ctx, db); if (rc != 0) { return rc; } } return 0; } int od_cfg_convert_model(const od_cfg_model_t *model, od_config_t *config, od_rules_t *rules, od_global_t *global, od_hba_rules_t *hba_rules, od_cfg_diag_list_t *diags) { od_list_t *i, *s; convert_ctx_t ctx; ctx.config = config; ctx.diags = diags; ctx.global = global; ctx.hba_rules = hba_rules; ctx.rules = rules; od_list_init(&ctx.spools); if (regcomp(&ctx.rfc952_hostname_regex, "^(\\.?(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9]))$", REG_EXTENDED)) { od_cfg_diag_error(diags, model->location, "can't compile hostname regex"); return -1; } int rc; rc = convert_shared_pools(&ctx, model); if (rc != 0) { goto to_return; } rc = convert_soft_oom(&ctx, &model->soft_oom); if (rc != 0) { goto to_return; } rc = convert_query_parsing(&ctx, &model->query_parsing); if (rc != 0) { goto to_return; } rc = convert_conn_drop_opts(&model->conn_drop_options, config); if (rc != 0) { goto to_return; } rc = convert_global(&model->global, config, global, hba_rules, diags); if (rc != 0) { goto to_return; } rc = convert_storages(model, &ctx.spools, rules, global, diags); if (rc != 0) { goto to_return; } rc = convert_listens(&ctx, model); if (rc != 0) { goto to_return; } #ifdef LDAP_FOUND rc = convert_ldap_endpoints(&ctx, model); if (rc != 0) { goto to_return; } #endif rc = convert_rules(&ctx, model); if (rc != 0) { goto to_return; } to_return: od_list_foreach_safe (&ctx.spools, i, s) { od_shared_pool_t *sp = od_container_of(i, od_shared_pool_t, link); od_list_unlink(&sp->link); od_list_init(&sp->link); od_shared_pool_unref(sp); } regfree(&ctx.rfc952_hostname_regex); return rc; } odyssey-1.6.0-rc1/sources/cfg/ctx.c000066400000000000000000000012541526064202200170650ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include void od_cfg_parse_ctx_init(od_cfg_parse_ctx_t *ctx, const char *filename, char *input, size_t input_size, od_cfg_model_t *model, od_cfg_diag_list_t *diags) { memset(ctx, 0, sizeof(*ctx)); ctx->filename = filename; ctx->input = input; ctx->input_size = input_size; ctx->model = model; ctx->diags = diags; ctx->lexer_line = 1; ctx->lexer_column = 1; ctx->lexer_offset = 0; } void od_cfg_parse_ctx_free(od_cfg_parse_ctx_t *ctx) { od_free(ctx->last_unknown_ident); ctx->last_unknown_ident = NULL; memset(ctx, 0, sizeof(*ctx)); } odyssey-1.6.0-rc1/sources/cfg/diag.c000066400000000000000000000067521526064202200172030ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include static char *od_cfg_vformat(const char *fmt, va_list args) { va_list copy; va_copy(copy, args); int len = vsnprintf(NULL, 0, fmt, copy); va_end(copy); if (len < 0) { return NULL; } char *buf = od_malloc((size_t)len + 1); if (buf == NULL) { return NULL; } vsnprintf(buf, (size_t)len + 1, fmt, args); return buf; } void od_cfg_diag_list_init(od_cfg_diag_list_t *diags) { diags->items = NULL; diags->count = 0; diags->capacity = 0; } void od_cfg_diag_list_free(od_cfg_diag_list_t *diags) { if (diags == NULL) { return; } for (size_t i = 0; i < diags->count; i++) { od_cfg_location_free(&diags->items[i].location); od_free(diags->items[i].message); od_free(diags->items[i].hint); } od_free(diags->items); diags->items = NULL; diags->count = 0; diags->capacity = 0; } static void od_cfg_diag_addv(od_cfg_diag_list_t *diags, od_cfg_diag_level_t level, od_cfg_location_t location, const char *fmt, va_list args) { if (diags->count == diags->capacity) { size_t new_capacity = diags->capacity == 0 ? 16 : diags->capacity * 2; od_cfg_diag_t *new_items = od_realloc( diags->items, sizeof(od_cfg_diag_t) * new_capacity); if (new_items == NULL) { return; } diags->items = new_items; diags->capacity = new_capacity; } od_cfg_diag_t *diag = &diags->items[diags->count++]; diag->level = level; diag->location = od_cfg_location_copy(location); diag->message = od_cfg_vformat(fmt, args); diag->hint = NULL; } void od_cfg_diag_error(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *fmt, ...) { va_list args; va_start(args, fmt); od_cfg_diag_addv(diags, OD_CFG_DIAG_ERROR, location, fmt, args); va_end(args); } void od_cfg_diag_error_with_hint(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *hint, const char *fmt, ...) { va_list args; va_start(args, fmt); od_cfg_diag_addv(diags, OD_CFG_DIAG_ERROR, location, fmt, args); va_end(args); if (hint != NULL && diags->count > 0) { od_cfg_diag_t *diag = &diags->items[diags->count - 1]; diag->hint = od_strdup(hint); } } void od_cfg_diag_warning(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *fmt, ...) { va_list args; va_start(args, fmt); od_cfg_diag_addv(diags, OD_CFG_DIAG_WARNING, location, fmt, args); va_end(args); } int od_cfg_diag_has_errors(const od_cfg_diag_list_t *diags) { for (size_t i = 0; i < diags->count; i++) { if (diags->items[i].level == OD_CFG_DIAG_ERROR) { return 1; } } return 0; } void od_cfg_diag_deprecated_ignored(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *option_name) { od_cfg_diag_warning(diags, location, "option '%s' is deprecated and ignored", option_name); } void od_cfg_diag_dumpf(FILE *file, const od_cfg_diag_list_t *diags) { for (size_t i = 0; i < diags->count; i++) { const od_cfg_diag_t *diag = &diags->items[i]; const char *level = diag->level == OD_CFG_DIAG_ERROR ? "error" : "warning"; if (diag->location.filename != NULL) { fprintf(file, "%s:%d:%d: %s: %s\n", diag->location.filename, diag->location.first_line, diag->location.first_column, level, diag->message ? diag->message : ""); } else { fprintf(file, "%s: %s\n", level, diag->message ? diag->message : ""); } if (diag->hint != NULL) { fprintf(file, " hint: %s\n", diag->hint); } } } odyssey-1.6.0-rc1/sources/cfg/keywords.c000066400000000000000000000224571526064202200201460ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef struct { const char *name; int token; } od_cfg_keyword_t; static const od_cfg_keyword_t keywords[] = { { "yes", YES }, { "no", NO }, { "daemonize", DAEMONIZE }, { "sequential_routing", SEQUENTIAL_ROUTING }, { "enable_online_restart", ENABLE_ONLINE_RESTART }, { "virtual_processing", VIRTUAL_PROCESSING }, { "graceful_die_on_errors", GRACEFUL_DIE_ON_ERRORS }, { "bindwith_reuseport", BINDWITH_REUSEPORT }, { "enable_host_watcher", ENABLE_HOST_WATCHER }, { "log_debug", LOG_DEBUG }, { "log_to_stdout", LOG_TO_STDOUT }, { "log_config", LOG_CONFIG }, { "log_session", LOG_SESSION }, { "log_query", LOG_QUERY }, { "log_stats", LOG_STATS }, { "log_async", LOG_ASYNC }, { "log_syslog", LOG_SYSLOG }, { "smart_search_path_enquoting", SMART_SEARCH_PATH_ENQUOTING }, { "nodelay", NODELAY }, { "disable_nolinger", DISABLE_NOLINGER }, { "log_general_stats_prom", LOG_GENERAL_STATS_PROM }, { "log_route_stats_prom", LOG_ROUTE_STATS_PROM }, { "pid_file", PID_FILE }, { "unix_socket_dir", UNIX_SOCKET_DIR }, { "unix_socket_mode", UNIX_SOCKET_MODE }, { "locks_dir", LOCKS_DIR }, { "external_auth_socket_path", EXTERNAL_AUTH_SOCKET_PATH }, { "availability_zone", AVAILABILITY_ZONE }, { "log_file", LOG_FILE }, { "log_format", LOG_FORMAT }, { "log_syslog_ident", LOG_SYSLOG_IDENT }, { "log_syslog_facility", LOG_SYSLOG_FACILITY }, { "cpu_affinity", CPU_AFFINITY }, { "hba_file", HBA_FILE }, { "priority", PRIORITY }, { "graceful_shutdown_timeout_ms", GRACEFUL_SHUTDOWN_TIMEOUT_MS }, { "log_queue_depth", LOG_QUEUE_DEPTH }, { "stats_interval", STATS_INTERVAL }, { "client_max", CLIENT_MAX }, { "client_max_routing", CLIENT_MAX_ROUTING }, { "accept_rate_limit", ACCEPT_RATE_LIMIT }, { "server_login_retry", SERVER_LOGIN_RETRY }, { "readahead", READAHEAD }, { "keepalive", KEEPALIVE }, { "keepalive_keep_interval", KEEPALIVE_KEEP_INTERVAL }, { "keepalive_probes", KEEPALIVE_PROBES }, { "keepalive_usr_timeout", KEEPALIVE_USR_TIMEOUT }, { "max_sigterms_to_die", MAX_SIGTERMS_TO_DIE }, { "backend_connect_timeout_ms", BACKEND_CONNECT_TIMEOUT_MS }, { "cancel_timeout_ms", CANCEL_TIMEOUT_MS }, { "cancel_queue_timeout_ms", CANCEL_QUEUE_TIMEOUT_MS }, { "cancel_max_inflight", CANCEL_MAX_INFLIGHT }, { "cancel_rate_limit", CANCEL_RATE_LIMIT }, { "resolvers", RESOLVERS }, { "dns_cache_ttl", DNS_CACHE_TTL }, { "cache_msg_gc_size", CACHE_MSG_GC_SIZE }, { "cache_msg_gc_count", CACHE_MSG_GC_COUNT }, { "cache_coroutine", CACHE_COROUTINE }, { "coroutine_stack_size", COROUTINE_STACK_SIZE }, { "system_coroutine_stack_size", SYSTEM_COROUTINE_STACK_SIZE }, { "promhttp_server_port", PROMHTTP_SERVER_PORT }, { "group_checker_interval", GROUP_CHECKER_INTERVAL }, { "shared_pool", SHARED_POOL }, { "pool_size", POOL_SIZE }, { "conn_drop_options", CONN_DROP_OPTIONS }, { "online_restart_drop_options", ONLINE_RESTART_DROP_OPTIONS }, { "drop_enabled", DROP_ENABLED }, { "rate", RATE }, { "interval_ms", INTERVAL_MS }, { "listen", LISTEN }, { "host", HOST }, { "port", PORT }, { "target_session_attrs", TARGET_SESSION_ATTRS }, { "client_login_timeout", CLIENT_LOGIN_TIMEOUT }, { "backlog", BACKLOG }, { "tls", TLS }, { "tls_ca_file", TLS_CA_FILE }, { "tls_key_file", TLS_KEY_FILE }, { "tls_cert_file", TLS_CERT_FILE }, { "tls_protocols", TLS_PROTOCOLS }, { "catchup_timeout", CATCHUP_TIMEOUT }, { "storage", STORAGE }, { "type", TYPE }, { "server_max_routing", SERVER_MAX_ROUTING }, { "endpoints_status_poll_interval", ENDPOINTS_STATUS_POLL_INTERVAL }, { "balancing", BALANCING }, { "method", METHOD }, { "show_notice_messages", SHOW_NOTICE_MESSAGES }, { "compression", COMPRESSION }, { "workers", WORKERS }, { "pipeline", PIPELINE }, { "cache", CACHE }, { "cache_chunk", CACHE_CHUNK }, { "packet_read_size", PACKET_READ_SIZE }, { "packet_write_queue", PACKET_WRITE_QUEUE }, { "soft_oom", SOFT_OOM }, { "limit", LIMIT }, { "process", PROCESS }, { "check_interval_ms", CHECK_INTERVAL_MS }, { "drop", DROP }, { "signal", SIGNAL }, { "max_rate", MAX_RATE }, { "ldap_endpoint", LDAP_ENDPOINT }, { "ldapserver", LDAP_SERVER }, { "ldapport", LDAP_PORT }, { "ldapprefix", LDAP_PREFIX }, { "ldapsuffix", LDAP_SUFFIX }, { "ldapbasedn", LDAP_BASEDN }, { "ldapbinddn", LDAP_BINDDN }, { "ldapbindpasswd", LDAP_BIND_PASSWD }, { "ldapsearchattribute", LDAP_SEARCH_ATTRIBUTE }, { "ldapscheme", LDAP_SCHEME }, { "ldapscope", LDAP_SCOPE }, { "ldapsearchfilter", LDAP_SEARCH_FILTER }, { "database", DATABASE }, { "default", DEFAULT }, { "user", USER }, { "local", LOCAL }, { "hostssl", HOSTSSL }, { "hostnossl", HOSTNOSSL }, { "ldap_storage_username", LDAP_STORAGE_USERNAME }, { "ldap_storage_password", LDAP_STORAGE_PASSWORD }, { "auth_common_name", AUTH_COMMON_NAME }, { "authentication", AUTHENTICATION }, { "auth_module", AUTH_MODULE }, { "enable_mdb_iamproxy_auth", ENABLE_MDB_IAMPROXY_AUTH }, { "mdb_iamproxy_socket_path", MDB_IAMPROXY_SOCKET_PATH }, { "auth_pam_service", AUTH_PAM_SERVICE }, { "auth_query", AUTH_QUERY }, { "auth_query_db", AUTH_QUERY_DB }, { "auth_query_user", AUTH_QUERY_USER }, { "password_passthrough", PASSWORD_PASSTHROUGH }, { "password", PASSWORD }, { "role", ROLE }, { "client_fwd_error", CLIENT_FWD_ERROR }, { "client_show_id", CLIENT_SHOW_ID }, { "reserve_session_server_connection", RESERVE_SESSION_SERVER_CONNECTION }, { "quantiles", QUANTILES }, { "application_name_add_host", APPLICATION_NAME_ADD_HOST }, { "server_drop_on_cached_plan_error", SERVER_DROP_ON_CACHED_PLAN_ERROR }, { "server_drop_on_oom", SERVER_DROP_ON_OOM }, { "server_lifetime", SERVER_LIFETIME }, { "server_pstmt_cache_size", SERVER_PSTMT_CACHE_SIZE }, { "ldap_pool_size", LDAP_POOL_SIZE }, { "ldap_pool_timeout", LDAP_POOL_TIMEOUT }, { "ldap_pool_ttl", LDAP_POOL_TTL }, { "maintain_params", MAINTAIN_PARAMS }, { "pool", POOL }, { "pool_routing", POOL_ROUTING }, { "min_pool_size", MIN_POOL_SIZE }, { "pool_timeout", POOL_TIMEOUT }, { "pool_ttl", POOL_TTL }, { "pool_discard", POOL_DISCARD }, { "pool_smart_discard", POOL_SMART_DISCARD }, { "pool_discard_query", POOL_DISCARD_QUERY }, { "pool_cancel", POOL_CANCEL }, { "pool_rollback", POOL_ROLLBACK }, { "pool_reset_timeout_ms", POOL_RESET_TIMEOUT_MS }, { "pool_reserve_prepared_statement", POOL_RESERVE_PREPARED_STATEMENT }, { "pool_pin_on_listen", POOL_PIN_ON_LISTEN }, { "pool_attach_check", POOL_ATTACH_CHECK }, { "pool_acquire_fail_fast", POOL_ACQUIRE_FAIL_FAST }, { "pool_notice_after_waiting_ms", POOL_NOTICE_AFTER_WAITING_MS }, { "pool_client_idle_timeout", POOL_CLIENT_IDLE_TIMEOUT }, { "pool_idle_in_transaction_timeout", POOL_IDLE_IN_TRANSACTION_TIMEOUT }, { "storage_db", STORAGE_DATABASE }, { "storage_user", STORAGE_USER }, { "storage_password", STORAGE_PASSWORD }, { "ldap_endpoint_name", LDAP_ENDPOINT_NAME }, { "ldap_storage_credentials_attr", LDAP_STORAGE_CREDENTIALS_ATTR }, { "ldap_storage_credentials", LDAP_STORAGE_CREDENTIALS }, { "watchdog_lag_query", WATCHDOG_LAG_QUERY }, { "watchdog_lag_interval", WATCHDOG_LAG_INTERVAL }, { "options", OPTIONS }, { "backend_startup_options", BACKEND_STARTUP_OPTIONS }, { "group_query", GROUP_QUERY }, { "group_query_user", GROUP_QUERY_USER }, { "group_query_db", GROUP_QUERY_DB }, { "catchup_checks", CATCHUP_CHECKS }, { "watchdog", WATCHDOG }, { "group", GROUP }, { "include", INCLUDE }, { "az_aware", AZ_AWARE }, { "virtual_transaction", VIRTUAL_TRANSACTION }, { "query_parsing", QUERY_PARSING }, { "mode", MODE }, { "disabled", DISABLED }, { "minimal", MINIMAL }, { "full", FULL }, { "mem_limit", MEM_LIMIT }, { "reserved_clients", RESERVED_CLIENTS }, { NULL, 0 } }; int od_cfg_keyword_lookup(const char *str, size_t len) { for (size_t i = 0; keywords[i].name != NULL; i++) { if (strlen(keywords[i].name) == len && memcmp(keywords[i].name, str, len) == 0) { return keywords[i].token; } } return 0; } static int levenshtein(const char *a, size_t alen, const char *b, size_t blen) { if (alen == 0) { return (int)blen; } if (blen == 0) { return (int)alen; } size_t n = alen + 1; size_t m = blen + 1; int *prev = od_malloc(sizeof(int) * m); int *curr = od_malloc(sizeof(int) * m); if (prev == NULL || curr == NULL) { od_free(prev); od_free(curr); return (int)((alen > blen) ? alen : blen); } for (size_t j = 0; j < m; j++) { prev[j] = (int)j; } for (size_t i = 1; i < n; i++) { curr[0] = (int)i; for (size_t j = 1; j < m; j++) { int cost = (a[i - 1] == b[j - 1]) ? 0 : 1; int del = prev[j] + 1; int ins = curr[j - 1] + 1; int sub = prev[j - 1] + cost; int min = del < ins ? del : ins; if (sub < min) { min = sub; } curr[j] = min; } int *tmp = prev; prev = curr; curr = tmp; } int result = prev[blen]; od_free(prev); od_free(curr); return result; } const char *od_cfg_keyword_suggest(const char *str, size_t len, int max_distance) { const char *best = NULL; int best_dist = max_distance + 1; for (size_t i = 0; keywords[i].name != NULL; i++) { size_t klen = strlen(keywords[i].name); if (klen > len) { if (klen - len > (size_t)max_distance) { continue; } } else { if (len - klen > (size_t)max_distance) { continue; } } int d = levenshtein(str, len, keywords[i].name, klen); if (d < best_dist) { best_dist = d; best = keywords[i].name; } } return best; } odyssey-1.6.0-rc1/sources/cfg/model.c000066400000000000000000001540511526064202200173730ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include void od_cfg_model_init(od_cfg_model_t *model) { memset(model, 0, sizeof(*model)); } static void print_depth(FILE *file, int depth) { for (int i = 0; i < depth; ++i) { fputc('\t', file); } } static void dump_bool(FILE *file, const char *name, int depth, const od_cfg_bool_field_t *f) { if (f->seen.is_set) { print_depth(file, depth); fprintf(file, "%s %s\n", name, f->value ? "yes" : "no"); } } static void dump_string(FILE *file, const char *name, int depth, const od_cfg_string_field_t *f) { if (f->seen.is_set) { print_depth(file, depth); fprintf(file, "%s \"%s\"\n", name, f->value); } } static void dump_int(FILE *file, const char *name, int depth, const od_cfg_int_field_t *f) { if (f->seen.is_set) { print_depth(file, depth); fprintf(file, "%s %d\n", name, f->value); } } static void dump_uint64(FILE *file, const char *name, int depth, const od_cfg_u64_field_t *f) { if (f->seen.is_set) { print_depth(file, depth); fprintf(file, "%s %llu\n", name, (unsigned long long)f->value); } } static void dump_shared_pool(FILE *file, const od_cfg_shared_pool_t *sp) { fprintf(file, "shared_pool \"%s\" {\n", sp->name); dump_int(file, "pool_size", 1, &sp->pool_size); fprintf(file, "}\n"); } static void dump_conn_drop(FILE *file, const od_cfg_conn_drop_options_t *cd) { if (cd->seen.is_set) { fprintf(file, "conn_drop_options {\n"); dump_bool(file, "drop_enabled", 1, &cd->drop_enabled); dump_int(file, "rate", 1, &cd->rate); dump_int(file, "interval_ms", 1, &cd->interval_ms); fprintf(file, "}\n"); } } static void dump_balancing(FILE *file, int indent, const od_cfg_balancing_t *b) { if (!b->seen.is_set) { return; } fprintf(file, "%*sbalancing {\n", indent, ""); if (b->method.seen.is_set) { fprintf(file, "%*smethod \"%s\" {\n", indent + 1, "", b->method.name); dump_bool(file, "az_aware", indent + 2, &b->method.az_aware); fprintf(file, "%*s}\n", indent + 1, ""); } dump_bool(file, "show_notice_messages", indent + 1, &b->show_notice_messages); fprintf(file, "%*s}\n", indent, ""); } static void dump_listen(FILE *file, const od_cfg_listen_t *l) { fprintf(file, "listen {\n"); dump_string(file, "host", 1, &l->host); dump_int(file, "port", 1, &l->port); dump_string(file, "target_session_attrs", 1, &l->target_session_attrs); dump_balancing(file, 1, &l->balancing); dump_int(file, "client_login_timeout", 1, &l->client_login_timeout); dump_int(file, "reserved_clients", 1, &l->reserved_clients); dump_int(file, "backlog", 1, &l->backlog); dump_string(file, "tls", 1, &l->tls); dump_string(file, "tls_ca_file", 1, &l->tls_ca_file); dump_string(file, "tls_key_file", 1, &l->tls_key_file); dump_string(file, "tls_cert_file", 1, &l->tls_cert_file); dump_string(file, "tls_protocols", 1, &l->tls_protocols); dump_int(file, "catchup_timeout", 1, &l->catchup_timeout); fprintf(file, "}\n"); } static void dump_user(FILE *file, const char *type, const od_cfg_route_t *user); static void dump_storage(FILE *file, const od_cfg_storage_t *s) { fprintf(file, "storage \"%s\" {\n", s->name); dump_string(file, "type", 1, &s->type); dump_string(file, "host", 1, &s->host); dump_int(file, "port", 1, &s->port); dump_string(file, "tls", 1, &s->tls); dump_string(file, "tls_ca_file", 1, &s->tls_ca_file); dump_string(file, "tls_key_file", 1, &s->tls_key_file); dump_string(file, "tls_cert_file", 1, &s->tls_cert_file); dump_string(file, "tls_protocols", 1, &s->tls_protocols); dump_int(file, "server_max_routing", 1, &s->server_max_routing); dump_int(file, "endpoints_status_poll_interval_ms", 1, &s->endpoints_status_poll_interval_ms); dump_balancing(file, 1, &s->balancing); if (s->watchdog != NULL) { dump_user(file, "watchdog", s->watchdog); } fprintf(file, "}\n"); } static void dump_soft_oom(FILE *file, const od_cfg_soft_oom_t *s) { if (!s->seen.is_set) { return; } fprintf(file, "soft_oom {\n"); dump_uint64(file, "limit", 1, &s->limit); dump_string(file, "process", 1, &s->process); dump_int(file, "check_interval_ms", 1, &s->check_interval_ms); if (s->drop.seen.is_set) { fprintf(file, "\tdrop {\n"); dump_string(file, "signal", 2, &s->drop.signal); dump_int(file, "max_rate", 2, &s->drop.max_rate); fprintf(file, "\t}\n"); } fprintf(file, "}\n"); } static void dump_ldap_endpoint(FILE *file, const od_cfg_ldap_endpoint_t *e) { fprintf(file, "ldap_endpoint \"%s\" {\n", e->name); dump_string(file, "ldapserver", 1, &e->ldapserver); dump_uint64(file, "ldapport", 1, &e->ldapport); dump_string(file, "ldapprefix", 1, &e->ldapprefix); dump_string(file, "ldapsuffix", 1, &e->ldapsuffix); dump_string(file, "ldapsearchattribute", 1, &e->ldapsearchattribute); dump_string(file, "ldapscope", 1, &e->ldapscope); dump_string(file, "ldapscheme", 1, &e->ldapscheme); dump_string(file, "ldapbasedn", 1, &e->ldapbasedn); dump_string(file, "ldapbinddn", 1, &e->ldapbinddn); dump_string(file, "ldapbindpasswd", 1, &e->ldapbindpasswd); dump_string(file, "ldapsearchfilter", 1, &e->ldapsearchfilter); fprintf(file, "}\n"); } static void dump_string_kvp_list(FILE *file, const char *name, int depth, const od_cfg_string_kvp_list_t *list) { if (!list->seen.is_set || list->pairs_count == 0) { return; } print_depth(file, depth); fprintf(file, "%s {\n", name); for (size_t i = 0; i < list->pairs_count; ++i) { print_depth(file, depth + 1); fprintf(file, "\"%s\" \"%s\"\n", list->pairs[i]->key, list->pairs[i]->value); } print_depth(file, depth); fprintf(file, "}\n"); } static void dump_ldap_storage_credentials(FILE *file, int depth, const od_cfg_ldap_storage_credentials_t *lsc) { print_depth(file, depth); fprintf(file, "ldap_storage_credentials \"%s\" {\n", lsc->name); dump_string(file, "ldap_storage_username", depth + 1, &lsc->ldap_storage_username); dump_string(file, "ldap_storage_password", depth + 1, &lsc->ldap_storage_password); print_depth(file, depth); fprintf(file, "}\n"); } static void dump_user(FILE *file, const char *type, const od_cfg_route_t *user) { if (user->is_default) { fprintf(file, "\t%s default ", type); } else { if (user->name != NULL) { fprintf(file, "\t%s \"%s\" ", type, user->name); } else { fprintf(file, "\t%s ", type); } } if (user->address_range != NULL) { fprintf(file, "\"%s\" ", user->address_range); } if (user->conn_type != OD_CFG_CONN_TYPE_DEFAULT) { switch (user->conn_type) { case OD_CFG_CONN_TYPE_LOCAL: fprintf(file, "local "); break; case OD_CFG_CONN_TYPE_HOST: fprintf(file, "host "); break; case OD_CFG_CONN_TYPE_HOSTSSL: fprintf(file, "hostssl "); break; case OD_CFG_CONN_TYPE_HOSTNOSSL: fprintf(file, "hostnossl "); break; default: abort(); } } fprintf(file, "{\n"); for (size_t i = 0; i < user->auth_common_names_count; ++i) { print_depth(file, 2); if (user->auth_common_names[i]->is_default) { fprintf(file, "auth_common_name default\n"); } else { fprintf(file, "auth_common_name \"%s\"\n", user->auth_common_names[i]->name); } } dump_string(file, "authentication", 2, &user->authentication); dump_string(file, "auth_module", 2, &user->auth_module); dump_bool(file, "enable_mdb_iamproxy_auth", 2, &user->enable_mdb_iamproxy_auth); dump_string(file, "mdb_iamproxy_socket_path", 2, &user->mdb_iamproxy_socket_path); dump_string(file, "auth_pam_service", 2, &user->auth_pam_service); dump_string(file, "target_session_attrs", 2, &user->target_session_attrs); dump_string(file, "auth_query", 2, &user->auth_query); dump_string(file, "auth_query_db", 2, &user->auth_query_db); dump_string(file, "auth_query_user", 2, &user->auth_query_user); dump_bool(file, "password_passthrough", 2, &user->password_passthrough); dump_string(file, "password", 2, &user->password); dump_string(file, "role", 2, &user->role); dump_int(file, "client_max", 2, &user->client_max); dump_bool(file, "client_fwd_error", 2, &user->client_fwd_error); dump_bool(file, "client_show_id", 2, &user->client_show_id); dump_bool(file, "reserve_session_server_connection", 2, &user->reserve_session_server_connection); dump_string(file, "quantiles", 2, &user->quantiles); dump_bool(file, "application_name_add_host", 2, &user->application_name_add_host); dump_bool(file, "server_drop_on_backend_plan_error", 2, &user->server_drop_on_backend_plan_error); dump_bool(file, "server_drop_on_oom", 2, &user->server_drop_on_oom); dump_uint64(file, "server_lifetime", 2, &user->server_lifetime); if (user->ldap_pool_size.seen.is_set) { print_depth(file, 2); fprintf(file, "# deprecated: ldap_pool_size %d\n", user->ldap_pool_size.value); } if (user->ldap_pool_timeout.seen.is_set) { print_depth(file, 2); fprintf(file, "# deprecated: ldap_pool_timeout %d\n", user->ldap_pool_timeout.value); } if (user->ldap_pool_ttl.seen.is_set) { print_depth(file, 2); fprintf(file, "# deprecated: ldap_pool_ttl %d\n", user->ldap_pool_ttl.value); } dump_bool(file, "maintain_params", 2, &user->maintain_params); dump_string(file, "pool", 2, &user->pool); dump_string(file, "pool_routing", 2, &user->pool_routing); dump_int(file, "min_pool_size", 2, &user->min_pool_size); dump_int(file, "pool_size", 2, &user->pool_size); dump_int(file, "pool_timeout", 2, &user->pool_timeout); dump_int(file, "pool_ttl", 2, &user->pool_ttl); dump_bool(file, "pool_discard", 2, &user->pool_discard); dump_bool(file, "pool_smart_discard", 2, &user->pool_smart_discard); dump_string(file, "pool_discard_query", 2, &user->pool_discard_query); dump_bool(file, "pool_cancel", 2, &user->pool_cancel); dump_bool(file, "pool_rollback", 2, &user->pool_rollback); dump_uint64(file, "pool_reset_timeout_ms", 2, &user->pool_reset_timeout_ms); dump_bool(file, "pool_reserve_prepared_statement", 2, &user->pool_reserve_prepared_statement); dump_int(file, "server_pstmt_cache_size", 2, &user->server_pstmt_cache_size); dump_bool(file, "pool_pin_on_listen", 2, &user->pool_pin_on_listen); dump_bool(file, "pool_attach_check", 2, &user->pool_attach_check); dump_bool(file, "pool_acquire_fail_fast", 2, &user->pool_acquire_fail_fast); dump_uint64(file, "pool_notice_after_waiting_ms", 2, &user->pool_notice_after_waiting_ms); dump_uint64(file, "pool_client_idle_timeout", 2, &user->pool_client_idle_timeout); dump_uint64(file, "pool_idle_in_transaction_timeout", 2, &user->pool_idle_in_transaction_timeout); dump_string(file, "shared_pool", 2, &user->shared_pool); dump_string(file, "storage", 2, &user->storage); dump_string(file, "storage_user", 2, &user->storage_user); dump_string(file, "storage_password", 2, &user->storage_password); dump_string(file, "storage_database", 2, &user->storage_database); dump_bool(file, "log_debug", 2, &user->log_debug); dump_bool(file, "log_query", 2, &user->log_query); dump_string(file, "ldap_endpoint_name", 2, &user->ldap_endpoint_name); dump_string(file, "ldap_storage_credentials_attr", 2, &user->ldap_storage_credentials_attr); for (size_t i = 0; i < user->ldap_storage_credentials_count; ++i) { dump_ldap_storage_credentials( file, 2, user->ldap_storage_credentials[i]); } dump_string(file, "watchdog_lag_query", 2, &user->watchdog_lag_query); dump_int(file, "watchdog_lag_interval", 2, &user->watchdog_lag_interval); dump_int(file, "catchup_timeout", 2, &user->catchup_timeout); dump_string_kvp_list(file, "pgoptions", 2, &user->pgoptions); dump_string_kvp_list(file, "backend_startup_options", 2, &user->backend_startup_options); dump_string(file, "group_query", 2, &user->group_query); dump_string(file, "group_query_user", 2, &user->group_query_user); dump_string(file, "group_query_db", 2, &user->group_query_db); dump_int(file, "keepalive", 2, &user->keepalive); dump_int(file, "keepalive_keep_interval", 2, &user->keepalive_keep_interval); dump_int(file, "keepalive_probes", 2, &user->keepalive_probes); dump_int(file, "keepalive_usr_timeout", 2, &user->keepalive_usr_timeout); fprintf(file, "\t}\n"); } static void dump_database(FILE *file, const od_cfg_database_t *db) { if (db->is_default) { fprintf(file, "database default {\n"); } else { fprintf(file, "database \"%s\" {\n", db->name); } for (size_t i = 0; i < db->users_count; ++i) { dump_user(file, "user", db->users[i]); } for (size_t i = 0; i < db->groups_count; ++i) { dump_user(file, "group", db->groups[i]); } fprintf(file, "}\n"); } void od_cfg_model_dumpf(FILE *file, const od_cfg_model_t *model) { dump_bool(file, "daemonize", 0, &model->global.daemonize); dump_bool(file, "sequential_routing", 0, &model->global.sequential_routing); dump_bool(file, "enable_online_restart", 0, &model->global.enable_online_restart); dump_bool(file, "virtual_processing", 0, &model->global.virtual_processing); dump_bool(file, "bindwith_reuseport", 0, &model->global.bindwith_reuseport); dump_bool(file, "enable_host_watcher", 0, &model->global.enable_host_watcher); dump_bool(file, "log_debug", 0, &model->global.log_debug); dump_bool(file, "log_to_stdout", 0, &model->global.log_to_stdout); dump_bool(file, "log_config", 0, &model->global.log_config); dump_bool(file, "log_session", 0, &model->global.log_session); dump_bool(file, "log_query", 0, &model->global.log_query); dump_bool(file, "log_stats", 0, &model->global.log_stats); dump_bool(file, "log_async", 0, &model->global.log_async); dump_bool(file, "log_syslog", 0, &model->global.log_syslog); dump_bool(file, "log_general_stats_prom", 0, &model->global.log_general_stats_prom); dump_bool(file, "log_route_stats_prom", 0, &model->global.log_route_stats_prom); dump_bool(file, "smart_search_path_enquoting", 0, &model->global.smart_search_path_enquoting); dump_bool(file, "nodelay", 0, &model->global.nodelay); dump_bool(file, "disable_nolinger", 0, &model->global.disable_nolinger); dump_string(file, "pid_file", 0, &model->global.pid_file); dump_string(file, "unix_socket_dir", 0, &model->global.unix_socket_dir); dump_string(file, "unix_socket_mode", 0, &model->global.unix_socket_mode); dump_string(file, "locks_dir", 0, &model->global.locks_dir); dump_string(file, "external_auth_socket_path", 0, &model->global.external_auth_socket_path); dump_string(file, "availability_zone", 0, &model->global.availability_zone); dump_string(file, "log_file", 0, &model->global.log_file); dump_string(file, "log_format", 0, &model->global.log_format); dump_string(file, "log_syslog_ident", 0, &model->global.log_syslog_ident); dump_string(file, "log_syslog_facility", 0, &model->global.log_syslog_facility); dump_string(file, "cpu_affinity", 0, &model->global.cpu_affinity); dump_string(file, "hba_file", 0, &model->global.hba_file); dump_int(file, "priority", 0, &model->global.priority); dump_int(file, "graceful_shutdown_timeout_ms", 0, &model->global.graceful_shutdown_timeout_ms); dump_int(file, "log_queue_depth", 0, &model->global.log_queue_depth); dump_int(file, "stats_interval", 0, &model->global.stats_interval); dump_int(file, "client_max", 0, &model->global.client_max); dump_int(file, "client_max_routing", 0, &model->global.client_max_routing); dump_int(file, "server_login_retry", 0, &model->global.server_login_retry); dump_int(file, "readahead", 0, &model->global.readahead); dump_int(file, "keepalive", 0, &model->global.keepalive); dump_int(file, "keepalive_keep_interval", 0, &model->global.keepalive_keep_interval); dump_int(file, "keepalive_probes", 0, &model->global.keepalive_probes); dump_int(file, "keepalive_usr_timeout", 0, &model->global.keepalive_usr_timeout); dump_int(file, "max_sigterms_to_die", 0, &model->global.max_sigterms_to_die); dump_int(file, "backend_connect_timeout_ms", 0, &model->global.backend_connect_timeout_ms); dump_int(file, "cancel_timeout_ms", 0, &model->global.cancel_timeout_ms); dump_int(file, "cancel_queue_timeout_ms", 0, &model->global.cancel_queue_timeout_ms); dump_int(file, "cancel_max_inflight", 0, &model->global.cancel_max_inflight); dump_int(file, "cancel_rate_limit", 0, &model->global.cancel_rate_limit); dump_int(file, "resolvers", 0, &model->global.resolvers); dump_int(file, "dns_cache_ttl", 0, &model->global.dns_cache_ttl); dump_int(file, "cache_msg_gc_size", 0, &model->global.cache_msg_gc_size); dump_int(file, "cache_msg_gc_count", 0, &model->global.cache_msg_gc_count); dump_int(file, "cache_coroutine", 0, &model->global.cache_coroutine); dump_int(file, "coroutine_stack_size", 0, &model->global.coroutine_stack_size); dump_int(file, "system_coroutine_stack_size", 0, &model->global.system_coroutine_stack_size); dump_int(file, "promhttp_server_port", 0, &model->global.promhttp_server_port); dump_int(file, "group_checker_interval", 0, &model->global.group_checker_interval); dump_int(file, "workers", 0, &model->global.workers); for (size_t i = 0; i < model->listens_count; ++i) { dump_listen(file, model->listens[i]); } for (size_t i = 0; i < model->storages_count; ++i) { dump_storage(file, model->storages[i]); } for (size_t i = 0; i < model->databases_count; ++i) { dump_database(file, model->databases[i]); } for (size_t i = 0; i < model->shared_pools_count; ++i) { dump_shared_pool(file, model->shared_pools[i]); } for (size_t i = 0; i < model->ldap_endpoints_count; ++i) { dump_ldap_endpoint(file, model->ldap_endpoints[i]); } dump_conn_drop(file, &model->conn_drop_options); dump_soft_oom(file, &model->soft_oom); } static void od_cfg_string_field_free(od_cfg_string_field_t *field) { od_cfg_seen_free(&field->seen); od_free(field->value); field->value = NULL; } static void od_cfg_int_field_free(od_cfg_int_field_t *field) { od_cfg_seen_free(&field->seen); } static void od_cfg_query_parsing_mode_free(od_cfg_query_parsing_mode_field_t *field) { od_cfg_seen_free(&field->seen); } static void od_cfg_bool_field_free(od_cfg_bool_field_t *field) { od_cfg_seen_free(&field->seen); } static void od_cfg_u64_field_free(od_cfg_u64_field_t *field) { od_cfg_seen_free(&field->seen); } void od_cfg_seen_set(od_cfg_seen_t *seen, od_cfg_location_t location) { seen->is_set = 1; seen->location = od_cfg_location_copy(location); } void od_cfg_seen_free(od_cfg_seen_t *seen) { od_cfg_location_free(&seen->location); } void od_cfg_seen_reset(od_cfg_seen_t *seen) { od_cfg_location_free(&seen->location); seen->is_set = 0; } od_cfg_listen_storage_t *od_cfg_listen_add_storage(od_cfg_listen_t *listen, od_cfg_location_t location, const char *name) { if (listen->storages_count >= listen->storages_capacity) { size_t new_capacity = listen->storages_capacity == 0 ? 16 : listen->storages_capacity * 2; od_cfg_listen_storage_t **tmp = od_realloc( listen->storages, sizeof(od_cfg_listen_storage_t *) * new_capacity); if (tmp == NULL) { return NULL; } listen->storages = tmp; listen->storages_capacity = new_capacity; } od_cfg_listen_storage_t *storage = od_malloc(sizeof(od_cfg_listen_storage_t)); if (storage == NULL) { return NULL; } char *copy = od_strdup(name); if (copy == NULL) { od_free(storage); return NULL; } storage->location = od_cfg_location_copy(location); storage->name = copy; listen->storages[listen->storages_count++] = storage; return storage; } static void od_cfg_balancing_free(od_cfg_balancing_t *b) { od_cfg_seen_free(&b->seen); od_cfg_location_free(&b->location); od_cfg_bool_field_free(&b->show_notice_messages); od_cfg_seen_free(&b->method.seen); od_cfg_location_free(&b->method.location); od_cfg_bool_field_free(&b->method.az_aware); od_free(b->method.name); } static void od_cfg_listen_free(od_cfg_listen_t *listen) { if (listen == NULL) { return; } od_cfg_string_field_free(&listen->host); od_cfg_int_field_free(&listen->port); od_cfg_string_field_free(&listen->target_session_attrs); od_cfg_balancing_free(&listen->balancing); od_cfg_int_field_free(&listen->client_login_timeout); od_cfg_int_field_free(&listen->backlog); od_cfg_string_field_free(&listen->tls); od_cfg_string_field_free(&listen->tls_ca_file); od_cfg_string_field_free(&listen->tls_key_file); od_cfg_string_field_free(&listen->tls_cert_file); od_cfg_string_field_free(&listen->tls_protocols); od_cfg_int_field_free(&listen->catchup_timeout); od_cfg_int_field_free(&listen->reserved_clients); for (size_t i = 0; i < listen->storages_count; ++i) { od_cfg_location_free(&listen->storages[i]->location); od_free(listen->storages[i]->name); od_free(listen->storages[i]); } od_free(listen->storages); od_cfg_location_free(&listen->location); od_free(listen); } static void od_cfg_user_route_free(od_cfg_route_t *user); static void od_cfg_storage_free(od_cfg_storage_t *storage) { if (storage == NULL) { return; } if (storage->watchdog) { od_cfg_user_route_free(storage->watchdog); } od_free(storage->name); od_cfg_string_field_free(&storage->type); od_cfg_string_field_free(&storage->host); od_cfg_int_field_free(&storage->port); od_cfg_string_field_free(&storage->tls); od_cfg_string_field_free(&storage->tls_ca_file); od_cfg_string_field_free(&storage->tls_key_file); od_cfg_string_field_free(&storage->tls_cert_file); od_cfg_string_field_free(&storage->tls_protocols); od_cfg_int_field_free(&storage->server_max_routing); od_cfg_int_field_free(&storage->endpoints_status_poll_interval_ms); od_cfg_balancing_free(&storage->balancing); od_cfg_location_free(&storage->location); od_cfg_location_free(&storage->name_location); od_free(storage); } static void od_cfg_shared_pool_free(od_cfg_shared_pool_t *pool) { if (pool == NULL) { return; } od_free(pool->name); od_cfg_int_field_free(&pool->pool_size); od_cfg_location_free(&pool->location); od_cfg_location_free(&pool->name_location); od_free(pool); } static void od_cfg_ldap_endpoint_free(od_cfg_ldap_endpoint_t *endpoint) { if (endpoint == NULL) { return; } od_free(endpoint->name); od_cfg_string_field_free(&endpoint->ldapserver); od_cfg_u64_field_free(&endpoint->ldapport); od_cfg_string_field_free(&endpoint->ldapprefix); od_cfg_string_field_free(&endpoint->ldapsuffix); od_cfg_string_field_free(&endpoint->ldapsearchattribute); od_cfg_string_field_free(&endpoint->ldapscope); od_cfg_string_field_free(&endpoint->ldapscheme); od_cfg_string_field_free(&endpoint->ldapbasedn); od_cfg_string_field_free(&endpoint->ldapbinddn); od_cfg_string_field_free(&endpoint->ldapbindpasswd); od_cfg_string_field_free(&endpoint->ldapsearchfilter); od_cfg_location_free(&endpoint->location); od_cfg_location_free(&endpoint->name_location); od_free(endpoint); } static void od_cfg_string_kvp_free(od_cfg_string_kvp_t *kvp) { od_free(kvp->key); od_free(kvp->value); od_cfg_location_free(&kvp->location); od_cfg_location_free(&kvp->key_location); od_cfg_location_free(&kvp->value_location); } static void od_cfg_string_kvp_list_free(od_cfg_string_kvp_list_t *l) { for (size_t i = 0; i < l->pairs_count; ++i) { od_cfg_string_kvp_free(l->pairs[i]); od_free(l->pairs[i]); } od_free(l->pairs); } static void od_cfg_user_route_free(od_cfg_route_t *user) { if (user == NULL) { return; } od_free(user->name); od_free(user->address_range); for (size_t i = 0; i < user->auth_common_names_count; ++i) { od_free(user->auth_common_names[i]->name); od_cfg_location_free(&user->auth_common_names[i]->location); od_cfg_location_free( &user->auth_common_names[i]->name_location); od_free(user->auth_common_names[i]); } od_free(user->auth_common_names); for (size_t i = 0; i < user->ldap_storage_credentials_count; ++i) { od_free(user->ldap_storage_credentials[i]->name); od_cfg_string_field_free(&user->ldap_storage_credentials[i] ->ldap_storage_username); od_cfg_string_field_free(&user->ldap_storage_credentials[i] ->ldap_storage_password); od_cfg_location_free( &user->ldap_storage_credentials[i]->location); od_cfg_location_free( &user->ldap_storage_credentials[i]->name_location); od_free(user->ldap_storage_credentials[i]); } od_free(user->ldap_storage_credentials); od_cfg_string_field_free(&user->authentication); od_cfg_string_field_free(&user->auth_module); od_cfg_bool_field_free(&user->enable_mdb_iamproxy_auth); od_cfg_string_field_free(&user->mdb_iamproxy_socket_path); od_cfg_string_field_free(&user->auth_pam_service); od_cfg_string_field_free(&user->target_session_attrs); od_cfg_string_field_free(&user->auth_query); od_cfg_string_field_free(&user->auth_query_db); od_cfg_string_field_free(&user->auth_query_user); od_cfg_bool_field_free(&user->password_passthrough); od_cfg_string_field_free(&user->password); od_cfg_string_field_free(&user->role); od_cfg_int_field_free(&user->client_max); od_cfg_bool_field_free(&user->client_fwd_error); od_cfg_bool_field_free(&user->client_show_id); od_cfg_bool_field_free(&user->reserve_session_server_connection); od_cfg_string_field_free(&user->quantiles); od_cfg_bool_field_free(&user->application_name_add_host); od_cfg_bool_field_free(&user->server_drop_on_backend_plan_error); od_cfg_bool_field_free(&user->server_drop_on_oom); od_cfg_u64_field_free(&user->server_lifetime); od_cfg_int_field_free(&user->ldap_pool_size); od_cfg_int_field_free(&user->ldap_pool_timeout); od_cfg_int_field_free(&user->ldap_pool_ttl); od_cfg_bool_field_free(&user->maintain_params); od_cfg_string_field_free(&user->pool); od_cfg_string_field_free(&user->pool_routing); od_cfg_int_field_free(&user->min_pool_size); od_cfg_int_field_free(&user->pool_size); od_cfg_int_field_free(&user->pool_timeout); od_cfg_int_field_free(&user->pool_ttl); od_cfg_bool_field_free(&user->pool_discard); od_cfg_bool_field_free(&user->pool_smart_discard); od_cfg_string_field_free(&user->pool_discard_query); od_cfg_bool_field_free(&user->pool_cancel); od_cfg_bool_field_free(&user->pool_rollback); od_cfg_u64_field_free(&user->pool_reset_timeout_ms); od_cfg_bool_field_free(&user->pool_reserve_prepared_statement); od_cfg_int_field_free(&user->server_pstmt_cache_size); od_cfg_bool_field_free(&user->pool_pin_on_listen); od_cfg_bool_field_free(&user->pool_attach_check); od_cfg_bool_field_free(&user->pool_acquire_fail_fast); od_cfg_u64_field_free(&user->pool_notice_after_waiting_ms); od_cfg_u64_field_free(&user->pool_client_idle_timeout); od_cfg_u64_field_free(&user->pool_idle_in_transaction_timeout); od_cfg_string_field_free(&user->shared_pool); od_cfg_string_field_free(&user->storage); od_cfg_string_field_free(&user->storage_database); od_cfg_string_field_free(&user->storage_user); od_cfg_string_field_free(&user->storage_password); od_cfg_bool_field_free(&user->log_debug); od_cfg_bool_field_free(&user->log_query); od_cfg_string_field_free(&user->ldap_endpoint_name); od_cfg_string_field_free(&user->ldap_storage_credentials_attr); od_cfg_string_field_free(&user->watchdog_lag_query); od_cfg_int_field_free(&user->watchdog_lag_interval); od_cfg_int_field_free(&user->catchup_timeout); od_cfg_string_field_free(&user->group_query); od_cfg_string_field_free(&user->group_query_user); od_cfg_string_field_free(&user->group_query_db); od_cfg_int_field_free(&user->keepalive); od_cfg_int_field_free(&user->keepalive_keep_interval); od_cfg_int_field_free(&user->keepalive_probes); od_cfg_int_field_free(&user->keepalive_usr_timeout); od_cfg_seen_free(&user->pgoptions.seen); od_cfg_location_free(&user->pgoptions.location); od_cfg_string_kvp_list_free(&user->pgoptions); od_cfg_seen_free(&user->backend_startup_options.seen); od_cfg_location_free(&user->backend_startup_options.location); od_cfg_string_kvp_list_free(&user->backend_startup_options); od_cfg_location_free(&user->location); od_cfg_location_free(&user->name_location); od_cfg_location_free(&user->address_range_location); od_cfg_location_free(&user->conn_type_location); od_free(user); } static void od_cfg_database_free(od_cfg_database_t *db) { if (db == NULL) { return; } for (size_t i = 0; i < db->users_count; ++i) { od_cfg_user_route_free(db->users[i]); } od_free(db->users); for (size_t i = 0; i < db->groups_count; ++i) { od_cfg_user_route_free(db->groups[i]); } od_free(db->groups); od_free(db->name); od_cfg_location_free(&db->location); od_cfg_location_free(&db->name_location); od_free(db); } void od_cfg_global_free(od_cfg_global_t *g) { /* global bool fields */ od_cfg_bool_field_free(&g->daemonize); od_cfg_bool_field_free(&g->sequential_routing); od_cfg_bool_field_free(&g->enable_online_restart); od_cfg_bool_field_free(&g->virtual_processing); od_cfg_bool_field_free(&g->virtual_transaction); od_cfg_bool_field_free(&g->bindwith_reuseport); od_cfg_bool_field_free(&g->enable_host_watcher); od_cfg_bool_field_free(&g->log_debug); od_cfg_bool_field_free(&g->log_to_stdout); od_cfg_bool_field_free(&g->log_config); od_cfg_bool_field_free(&g->log_session); od_cfg_bool_field_free(&g->log_query); od_cfg_bool_field_free(&g->log_stats); od_cfg_bool_field_free(&g->log_async); od_cfg_bool_field_free(&g->log_syslog); od_cfg_bool_field_free(&g->smart_search_path_enquoting); od_cfg_bool_field_free(&g->nodelay); od_cfg_bool_field_free(&g->disable_nolinger); od_cfg_bool_field_free(&g->log_general_stats_prom); od_cfg_bool_field_free(&g->log_route_stats_prom); /* global string fields */ od_cfg_string_field_free(&g->pid_file); od_cfg_string_field_free(&g->unix_socket_dir); od_cfg_string_field_free(&g->unix_socket_mode); od_cfg_string_field_free(&g->locks_dir); od_cfg_string_field_free(&g->external_auth_socket_path); od_cfg_string_field_free(&g->availability_zone); od_cfg_string_field_free(&g->log_file); od_cfg_string_field_free(&g->log_format); od_cfg_string_field_free(&g->log_syslog_ident); od_cfg_string_field_free(&g->log_syslog_facility); od_cfg_string_field_free(&g->cpu_affinity); od_cfg_string_field_free(&g->hba_file); /* global int fields */ od_cfg_int_field_free(&g->priority); od_cfg_int_field_free(&g->graceful_shutdown_timeout_ms); od_cfg_int_field_free(&g->log_queue_depth); od_cfg_int_field_free(&g->stats_interval); od_cfg_int_field_free(&g->client_max); od_cfg_int_field_free(&g->client_max_routing); od_cfg_int_field_free(&g->accept_rate_limit); od_cfg_int_field_free(&g->server_login_retry); od_cfg_int_field_free(&g->readahead); od_cfg_int_field_free(&g->keepalive); od_cfg_int_field_free(&g->keepalive_keep_interval); od_cfg_int_field_free(&g->keepalive_probes); od_cfg_int_field_free(&g->keepalive_usr_timeout); od_cfg_int_field_free(&g->max_sigterms_to_die); od_cfg_int_field_free(&g->backend_connect_timeout_ms); od_cfg_int_field_free(&g->cancel_timeout_ms); od_cfg_int_field_free(&g->cancel_queue_timeout_ms); od_cfg_int_field_free(&g->cancel_max_inflight); od_cfg_int_field_free(&g->cancel_rate_limit); od_cfg_int_field_free(&g->resolvers); od_cfg_int_field_free(&g->dns_cache_ttl); od_cfg_int_field_free(&g->cache_msg_gc_size); od_cfg_int_field_free(&g->cache_msg_gc_count); od_cfg_int_field_free(&g->cache_coroutine); od_cfg_int_field_free(&g->coroutine_stack_size); od_cfg_int_field_free(&g->system_coroutine_stack_size); od_cfg_int_field_free(&g->promhttp_server_port); od_cfg_int_field_free(&g->group_checker_interval); od_cfg_int_field_free(&g->workers); memset(g, 0, sizeof(*g)); } void od_cfg_model_free(od_cfg_model_t *model) { od_cfg_global_free(&model->global); od_cfg_seen_free(&model->conn_drop_options.seen); od_cfg_location_free(&model->conn_drop_options.location); od_cfg_bool_field_free(&model->conn_drop_options.drop_enabled); od_cfg_int_field_free(&model->conn_drop_options.rate); od_cfg_int_field_free(&model->conn_drop_options.interval_ms); od_cfg_seen_free(&model->soft_oom.seen); od_cfg_location_free(&model->soft_oom.location); od_cfg_u64_field_free(&model->soft_oom.limit); od_cfg_string_field_free(&model->soft_oom.process); od_cfg_int_field_free(&model->soft_oom.check_interval_ms); od_cfg_seen_free(&model->soft_oom.drop.seen); od_cfg_location_free(&model->soft_oom.drop.location); od_cfg_string_field_free(&model->soft_oom.drop.signal); od_cfg_int_field_free(&model->soft_oom.drop.max_rate); od_cfg_query_parsing_mode_free(&model->query_parsing.mode); od_cfg_u64_field_free(&model->query_parsing.mem_limit); od_cfg_seen_free(&model->query_parsing.seen); for (size_t i = 0; i < model->databases_count; ++i) { od_cfg_database_free(model->databases[i]); } od_free(model->databases); for (size_t i = 0; i < model->storages_count; ++i) { od_cfg_storage_free(model->storages[i]); } od_free(model->storages); for (size_t i = 0; i < model->listens_count; ++i) { od_cfg_listen_free(model->listens[i]); } od_free(model->listens); for (size_t i = 0; i < model->shared_pools_count; ++i) { od_cfg_shared_pool_free(model->shared_pools[i]); } od_free(model->shared_pools); for (size_t i = 0; i < model->ldap_endpoints_count; ++i) { od_cfg_ldap_endpoint_free(model->ldap_endpoints[i]); } od_free(model->ldap_endpoints); od_cfg_location_free(&model->location); memset(model, 0, sizeof(*model)); } od_cfg_route_t *od_cfg_storage_add_watchdog(od_cfg_storage_t *s, od_cfg_location_t loc) { s->watchdog = od_malloc(sizeof(*s->watchdog)); if (s->watchdog == NULL) { return NULL; } memset(s->watchdog, 0, sizeof(*s->watchdog)); s->watchdog->location = od_cfg_location_copy(loc); return s->watchdog; } od_cfg_listen_t *od_cfg_model_add_listen(od_cfg_model_t *model, od_cfg_location_t location) { if (model->listens_count == model->listens_capacity) { size_t new_capacity = model->listens_capacity == 0 ? 16 : model->listens_capacity * 2; od_cfg_listen_t **new_items = od_realloc(model->listens, sizeof(od_cfg_listen_t *) * new_capacity); if (new_items == NULL) { return NULL; } model->listens = new_items; model->listens_capacity = new_capacity; } od_cfg_listen_t *listen = od_malloc(sizeof(*listen)); if (listen == NULL) { return NULL; } memset(listen, 0, sizeof(*listen)); listen->location = od_cfg_location_copy(location); model->listens[model->listens_count++] = listen; return listen; } od_cfg_string_kvp_t * od_cfg_string_kvp_list_add(od_cfg_string_kvp_list_t *list, od_cfg_location_t location, char *key, od_cfg_location_t key_location, char *value, od_cfg_location_t value_location) { if (list->pairs_count == list->pairs_capacity) { size_t new_capacity = list->pairs_capacity == 0 ? 16 : list->pairs_capacity * 2; od_cfg_string_kvp_t **new_items = od_realloc(list->pairs, sizeof(od_cfg_string_kvp_t *) * new_capacity); if (new_items == NULL) { return NULL; } list->pairs = new_items; list->pairs_capacity = new_capacity; } od_cfg_string_kvp_t *kvp = od_malloc(sizeof(*kvp)); if (kvp == NULL) { return NULL; } memset(kvp, 0, sizeof(*kvp)); kvp->location = od_cfg_location_copy(location); kvp->key = key; kvp->key_location = od_cfg_location_copy(key_location); kvp->value = value; kvp->value_location = od_cfg_location_copy(value_location); list->pairs[list->pairs_count++] = kvp; return kvp; } od_cfg_ldap_storage_credentials_t * od_cfg_user_route_add_ldap_creds(od_cfg_route_t *r, od_cfg_location_t location, char *name) { if (r->ldap_storage_credentials_count == r->ldap_storage_credentials_capacity) { size_t new_capacity = r->ldap_storage_credentials_capacity == 0 ? 16 : r->ldap_storage_credentials_capacity * 2; od_cfg_ldap_storage_credentials_t **new_items = od_realloc(r->ldap_storage_credentials, sizeof(od_cfg_ldap_storage_credentials_t *) * new_capacity); if (new_items == NULL) { return NULL; } r->ldap_storage_credentials = new_items; r->ldap_storage_credentials_capacity = new_capacity; } od_cfg_ldap_storage_credentials_t *c = od_malloc(sizeof(*c)); if (c == NULL) { return NULL; } memset(c, 0, sizeof(*c)); c->location = od_cfg_location_copy(location); c->name = name; c->name_location = od_cfg_location_copy(location); r->ldap_storage_credentials[r->ldap_storage_credentials_count++] = c; return c; } od_cfg_rule_auth_common_name_t *od_cfg_user_route_add_auth_common_name( od_cfg_route_t *r, char *name, int is_default, od_cfg_location_t location, od_cfg_location_t name_location) { if (r->auth_common_names_count == r->auth_common_names_capacity) { size_t new_capacity = r->auth_common_names_capacity == 0 ? 16 : r->auth_common_names_capacity * 2; od_cfg_rule_auth_common_name_t **new_items = od_realloc(r->auth_common_names, sizeof(od_cfg_rule_auth_common_name_t *) * new_capacity); if (new_items == NULL) { return NULL; } r->auth_common_names = new_items; r->auth_common_names_capacity = new_capacity; } od_cfg_rule_auth_common_name_t *cn = od_malloc(sizeof(*cn)); if (cn == NULL) { return NULL; } memset(cn, 0, sizeof(*cn)); cn->name = name; cn->location = od_cfg_location_copy(location); cn->name_location = od_cfg_location_copy(name_location); cn->is_default = is_default; r->auth_common_names[r->auth_common_names_count++] = cn; return cn; } od_cfg_route_t *od_cfg_database_add_user( od_cfg_database_t *db, char *name, int is_default, od_cfg_location_t name_location, od_cfg_location_t location, char *addr_range, od_cfg_location_t addr_range_location, od_cfg_conn_type_t conn_type, od_cfg_location_t conn_type_location) { if (db->users_count == db->users_capacity) { size_t new_capacity = db->users_capacity == 0 ? 16 : db->users_capacity * 2; od_cfg_route_t **new_items = od_realloc( db->users, sizeof(od_cfg_route_t *) * new_capacity); if (new_items == NULL) { return NULL; } db->users = new_items; db->users_capacity = new_capacity; } if (is_default) { name = od_strdup("default_user"); if (name == NULL) { return NULL; } } od_cfg_route_t *user = od_malloc(sizeof(*user)); if (user == NULL) { if (is_default) { od_free(name); } return NULL; } memset(user, 0, sizeof(*user)); user->name = name; user->name_location = od_cfg_location_copy(name_location); user->is_default = is_default; user->location = od_cfg_location_copy(location); user->address_range = addr_range; user->address_range_location = od_cfg_location_copy(addr_range_location); user->conn_type = conn_type; user->conn_type_location = od_cfg_location_copy(conn_type_location); db->users[db->users_count++] = user; return user; } od_cfg_route_t *od_cfg_database_add_group(od_cfg_database_t *db, char *name, od_cfg_location_t name_location, od_cfg_location_t location, char *addr_range, od_cfg_location_t addr_range_location, od_cfg_conn_type_t conn_type, od_cfg_location_t conn_type_location) { if (db->groups_count == db->groups_capacity) { size_t new_capacity = db->groups_capacity == 0 ? 16 : db->groups_capacity * 2; od_cfg_route_t **new_items = od_realloc( db->groups, sizeof(od_cfg_route_t *) * new_capacity); if (new_items == NULL) { return NULL; } db->groups = new_items; db->groups_capacity = new_capacity; } od_cfg_route_t *group = od_malloc(sizeof(*group)); if (group == NULL) { return NULL; } memset(group, 0, sizeof(*group)); group->name = name; group->name_location = od_cfg_location_copy(name_location); group->location = od_cfg_location_copy(location); group->address_range = addr_range; group->address_range_location = od_cfg_location_copy(addr_range_location); group->conn_type = conn_type; group->conn_type_location = od_cfg_location_copy(conn_type_location); db->groups[db->groups_count++] = group; return group; } od_cfg_database_t *od_cfg_model_add_database(od_cfg_model_t *model, char *name, int is_default, od_cfg_location_t name_location, od_cfg_location_t location) { if (model->databases_count == model->databases_capacity) { size_t new_capacity = model->databases_capacity == 0 ? 16 : model->databases_capacity * 2; od_cfg_database_t **new_items = od_realloc(model->databases, sizeof(od_cfg_database_t *) * new_capacity); if (new_items == NULL) { return NULL; } model->databases = new_items; model->databases_capacity = new_capacity; } if (is_default) { name = od_strdup("default_db"); if (name == NULL) { return NULL; } } od_cfg_database_t *database = od_malloc(sizeof(*database)); if (database == NULL) { if (is_default) { od_free(name); } return NULL; } memset(database, 0, sizeof(*database)); database->location = od_cfg_location_copy(location); database->name = name; database->is_default = is_default; database->name_location = od_cfg_location_copy(name_location); model->databases[model->databases_count++] = database; return database; } od_cfg_storage_t *od_cfg_model_add_storage(od_cfg_model_t *model, char *name, od_cfg_location_t name_location, od_cfg_location_t location) { if (model->storages_count == model->storages_capacity) { size_t new_capacity = model->storages_capacity == 0 ? 16 : model->storages_capacity * 2; od_cfg_storage_t **new_items = od_realloc(model->storages, sizeof(od_cfg_storage_t *) * new_capacity); if (new_items == NULL) { return NULL; } model->storages = new_items; model->storages_capacity = new_capacity; } od_cfg_storage_t *storage = od_malloc(sizeof(*storage)); if (storage == NULL) { return NULL; } memset(storage, 0, sizeof(*storage)); storage->location = od_cfg_location_copy(location); storage->name = name; storage->name_location = od_cfg_location_copy(name_location); model->storages[model->storages_count++] = storage; return storage; } static int od_cfg_check_duplicate(od_cfg_diag_list_t *diags, od_cfg_seen_t *seen, od_cfg_location_t location, const char *field_name) { if (!seen->is_set) { return 0; } od_cfg_diag_error(diags, location, "duplicate option '%s'", field_name); /* * TODO: add hint * previously defined at file:line:column */ return -1; } int od_cfg_set_bool(od_cfg_diag_list_t *diags, od_cfg_bool_field_t *field, int value, od_cfg_location_t location, const char *field_name) { if (od_cfg_check_duplicate(diags, &field->seen, location, field_name) != 0) { return -1; } field->value = value ? 1 : 0; od_cfg_seen_set(&field->seen, location); return 0; } int od_cfg_set_int(od_cfg_diag_list_t *diags, od_cfg_int_field_t *field, int value, od_cfg_location_t location, const char *field_name) { if (od_cfg_check_duplicate(diags, &field->seen, location, field_name) != 0) { return -1; } field->value = value; od_cfg_seen_set(&field->seen, location); return 0; } int od_cfg_set_int_from_i64(od_cfg_diag_list_t *diags, od_cfg_int_field_t *field, int64_t value, od_cfg_location_t location, const char *field_name) { if (value < INT_MIN || value > INT_MAX) { od_cfg_diag_error(diags, location, "value for option '%s' is out of int range", field_name); return -1; } return od_cfg_set_int(diags, field, (int)value, location, field_name); } int od_cfg_set_int_range_from_i64(od_cfg_diag_list_t *diags, od_cfg_int_field_t *field, int64_t value, int min, int max, od_cfg_location_t location, const char *field_name) { if (min > max) { od_cfg_diag_error( diags, location, "internal error: invalid range for option '%s'", field_name); return -1; } if (value < (int64_t)INT_MIN || value > (int64_t)INT_MAX) { od_cfg_diag_error(diags, location, "value '%s' = %lld is out of integer bounds", field_name, (long long)value); return -1; } if (value < min || value > max) { od_cfg_diag_error( diags, location, "value for option '%s' must be between %d and %d", field_name, min, max); return -1; } return od_cfg_set_int(diags, field, (int)value, location, field_name); } int od_cfg_set_u64(od_cfg_diag_list_t *diags, od_cfg_u64_field_t *field, uint64_t value, od_cfg_location_t location, const char *field_name) { if (od_cfg_check_duplicate(diags, &field->seen, location, field_name) != 0) { return -1; } field->value = value; od_cfg_seen_set(&field->seen, location); return 0; } static int od_cfg_validate_shared_pools(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { for (size_t i = 0; i < model->shared_pools_count; i++) { od_cfg_shared_pool_t *pool = model->shared_pools[i]; if (pool->name == NULL || pool->name[0] == 0) { od_cfg_diag_error(diags, pool->name_location, "shared_pool name cannot be empty"); } if (!pool->pool_size.seen.is_set) { od_cfg_diag_error( diags, pool->location, "pool_size is required in shared_pool '%s'", pool->name ? pool->name : ""); } else if (pool->pool_size.value <= 0) { od_cfg_diag_error( diags, pool->pool_size.seen.location, "shared_pool '%s' pool_size must be positive", pool->name); } for (size_t j = 0; j < i; j++) { od_cfg_shared_pool_t *prev = model->shared_pools[j]; if (strcmp(prev->name, pool->name) == 0) { od_cfg_diag_error( diags, pool->name_location, "duplicate shared_pool definition '%s'", pool->name); break; } } } return od_cfg_diag_has_errors(diags) ? -1 : 0; } static int od_cfg_validate_ldap_endpoints(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { if (model->ldap_endpoints_count == 0) { return 0; } #ifndef LDAP_FOUND for (size_t i = 0; i < model->ldap_endpoints_count; i++) { od_cfg_diag_error(diags, model->ldap_endpoints[i]->location, "LDAP is not supported by this build"); } return -1; #endif for (size_t i = 0; i < model->ldap_endpoints_count; i++) { od_cfg_ldap_endpoint_t *endpoint = model->ldap_endpoints[i]; if (endpoint->name == NULL || endpoint->name[0] == 0) { od_cfg_diag_error(diags, endpoint->name_location, "ldap_endpoint name cannot be empty"); return -1; } for (size_t j = 0; j < i; j++) { od_cfg_ldap_endpoint_t *prev = model->ldap_endpoints[j]; if (strcmp(prev->name, endpoint->name) == 0) { od_cfg_diag_error( diags, endpoint->name_location, "duplicate ldap endpoint definition: %s", endpoint->name); return -1; } } if (endpoint->ldapport.seen.is_set && endpoint->ldapport.value > 65535) { od_cfg_diag_error( diags, endpoint->ldapport.seen.location, "ldapport must be between 0 and 65535"); return -1; } } return 0; } static int od_cfg_validate_conn_drop_options(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { od_cfg_conn_drop_options_t *opts = &model->conn_drop_options; if (!opts->seen.is_set) { return 0; } if (opts->interval_ms.seen.is_set && opts->interval_ms.value <= 0) { od_cfg_diag_error( diags, opts->interval_ms.seen.location, "interval for conn_drop_options must be positive"); return -1; } return 0; } static int od_cfg_validate_soft_oom(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { od_cfg_soft_oom_t *so = &model->soft_oom; if (!so->seen.is_set) { return 0; } if (so->seen.is_set && (!so->limit.seen.is_set || so->limit.value == 0)) { od_cfg_diag_error(diags, so->location, "limit is not set for soft_oom"); return -1; } return 0; } static int od_cfg_validate_listens(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { if (model->listens_count == 0) { od_cfg_diag_error(diags, model->location, "no listen sections defined"); return -1; } for (size_t i = 0; i < model->listens_count; i++) { od_cfg_listen_t *l = model->listens[i]; if (!l->port.seen.is_set) { od_cfg_diag_error(diags, l->location, "listen section is missing 'port'"); } if (l->tls.seen.is_set) { const char *tls = l->tls.value; if (strcmp(tls, "disable") != 0 && strcmp(tls, "allow") != 0 && strcmp(tls, "prefer") != 0 && strcmp(tls, "require") != 0 && strcmp(tls, "verify_ca") != 0 && strcmp(tls, "verify_full") != 0) { od_cfg_diag_error(diags, l->tls.seen.location, "unknown tls mode '%s'", tls); } } } return od_cfg_diag_has_errors(diags) ? -1 : 0; } static int od_cfg_validate_storages(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { if (model->storages_count == 0) { od_cfg_diag_error(diags, model->location, "no storage sections defined"); return -1; } for (size_t i = 0; i < model->storages_count; i++) { od_cfg_storage_t *s = model->storages[i]; if (s->name == NULL || s->name[0] == '\0') { od_cfg_diag_error(diags, s->location, "storage name cannot be empty"); continue; } if (!s->type.seen.is_set) { od_cfg_diag_error(diags, s->location, "storage '%s': 'type' is required", s->name); } else if (strcmp(s->type.value, "remote") != 0 && strcmp(s->type.value, "local") != 0) { od_cfg_diag_error(diags, s->type.seen.location, "storage '%s': unknown type '%s'", s->name, s->type.value); } /* duplicate name check */ for (size_t j = 0; j < i; j++) { if (strcmp(model->storages[j]->name, s->name) == 0) { od_cfg_diag_error( diags, s->location, "duplicate storage definition '%s'", s->name); break; } } } return od_cfg_diag_has_errors(diags) ? -1 : 0; } static int od_cfg_validate_route(const od_cfg_route_t *route, od_cfg_model_t *model, od_cfg_diag_list_t *diags) { /* storage reference must exist */ if (route->storage.seen.is_set) { int found = 0; for (size_t k = 0; k < model->storages_count; k++) { if (strcmp(model->storages[k]->name, route->storage.value) == 0) { found = 1; break; } } if (!found) { od_cfg_diag_error(diags, route->storage.seen.location, "unknown storage '%s'", route->storage.value); } } /* shared_pool reference must exist */ if (route->shared_pool.seen.is_set) { int found = 0; for (size_t k = 0; k < model->shared_pools_count; k++) { if (strcmp(model->shared_pools[k]->name, route->shared_pool.value) == 0) { found = 1; break; } } if (!found) { od_cfg_diag_error(diags, route->shared_pool.seen.location, "unknown shared_pool '%s'", route->shared_pool.value); } } /* ldap_endpoint reference must exist */ if (route->ldap_endpoint_name.seen.is_set) { int found = 0; for (size_t k = 0; k < model->ldap_endpoints_count; k++) { if (strcmp(model->ldap_endpoints[k]->name, route->ldap_endpoint_name.value) == 0) { found = 1; break; } } if (!found) { od_cfg_diag_error( diags, route->ldap_endpoint_name.seen.location, "unknown ldap_endpoint '%s'", route->ldap_endpoint_name.value); } } return 0; } static int od_cfg_validate_db(od_cfg_database_t *db, od_cfg_model_t *model, od_cfg_diag_list_t *diags) { if (db->name == NULL || db->name[0] == '\0') { od_cfg_diag_error(diags, db->location, "invalid name for database"); return -1; } for (size_t i = 0; i < db->users_count; i++) { od_cfg_validate_route(db->users[i], model, diags); } for (size_t i = 0; i < db->groups_count; i++) { od_cfg_validate_route(db->groups[i], model, diags); } return 0; } static int od_cfg_validate_dbs(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { for (size_t i = 0; i < model->databases_count; ++i) { od_cfg_database_t *db = model->databases[i]; od_cfg_validate_db(db, model, diags); } return od_cfg_diag_has_errors(diags) ? -1 : 0; } int od_cfg_validate_model(od_cfg_model_t *model, od_cfg_diag_list_t *diags) { int rc; rc = od_cfg_validate_listens(model, diags); if (rc != 0) { return rc; } rc = od_cfg_validate_storages(model, diags); if (rc != 0) { return rc; } rc = od_cfg_validate_shared_pools(model, diags); if (rc != 0) { return rc; } rc = od_cfg_validate_conn_drop_options(model, diags); if (rc != 0) { return rc; } rc = od_cfg_validate_soft_oom(model, diags); if (rc != 0) { return rc; } rc = od_cfg_validate_ldap_endpoints(model, diags); if (rc != 0) { return rc; } rc = od_cfg_validate_dbs(model, diags); if (rc != 0) { return rc; } return 0; } int od_cfg_set_string(od_cfg_diag_list_t *diags, od_cfg_string_field_t *field, char *value, od_cfg_location_t location, const char *field_name) { if (od_cfg_check_duplicate(diags, &field->seen, location, field_name) != 0) { od_free(value); return -1; } field->value = value; od_cfg_seen_set(&field->seen, location); return 0; } int od_cfg_set_query_parsing_mode(od_cfg_diag_list_t *diags, od_cfg_query_parsing_mode_field_t *field, od_cfg_query_parsing_mode_type_t value, od_cfg_location_t location, const char *field_name) { if (od_cfg_check_duplicate(diags, &field->seen, location, field_name) != 0) { return -1; } field->value = value; od_cfg_seen_set(&field->seen, location); return 0; } od_cfg_shared_pool_t * od_cfg_model_add_shared_pool(od_cfg_model_t *model, char *name, od_cfg_location_t name_location, od_cfg_location_t location) { if (model->shared_pools_count == model->shared_pools_capacity) { size_t new_capacity = model->shared_pools_capacity == 0 ? 16 : model->shared_pools_capacity * 2; od_cfg_shared_pool_t **new_items = od_realloc(model->shared_pools, sizeof(*model->shared_pools) * new_capacity); if (new_items == NULL) { return NULL; } model->shared_pools = new_items; model->shared_pools_capacity = new_capacity; } od_cfg_shared_pool_t *pool = od_malloc(sizeof(*pool)); if (pool == NULL) { return NULL; } memset(pool, 0, sizeof(*pool)); pool->location = od_cfg_location_copy(location); pool->name = name; pool->name_location = od_cfg_location_copy(name_location); model->shared_pools[model->shared_pools_count++] = pool; return pool; } od_cfg_ldap_endpoint_t * od_cfg_model_add_ldap_endpoint(od_cfg_model_t *model, char *name, od_cfg_location_t name_location, od_cfg_location_t location) { if (model->ldap_endpoints_count == model->ldap_endpoints_capacity) { size_t new_capacity = model->ldap_endpoints_capacity == 0 ? 16 : model->ldap_endpoints_capacity * 2; od_cfg_ldap_endpoint_t **new_items = od_realloc( model->ldap_endpoints, sizeof(*model->ldap_endpoints) * new_capacity); if (new_items == NULL) { return NULL; } model->ldap_endpoints = new_items; model->ldap_endpoints_capacity = new_capacity; } od_cfg_ldap_endpoint_t *endpoint = od_malloc(sizeof(*endpoint)); if (endpoint == NULL) { return NULL; } memset(endpoint, 0, sizeof(*endpoint)); endpoint->location = od_cfg_location_copy(location); endpoint->name = name; endpoint->name_location = od_cfg_location_copy(name_location); model->ldap_endpoints[model->ldap_endpoints_count++] = endpoint; return endpoint; } odyssey-1.6.0-rc1/sources/cfg/parse.y000066400000000000000000001730111526064202200174300ustar00rootroot00000000000000%start config %define api.pure full %define parse.error verbose %locations %parse-param { yyscan_t scanner } %parse-param { od_cfg_parse_ctx_t *ctx } %lex-param { yyscan_t scanner } /* * machinarium stacks are small, so need to use heap and * limit initial size */ %code top { #define YYINITDEPTH 32 #define YYMAXDEPTH 200 #define YYSTACK_USE_ALLOCA 0 /* * default YYLLOC_DEFAULT only copies first/last line and column, * leaving our custom `filename` and `first_offset`/`last_offset` fields * uninitialized. This propagates filename and offsets from the first * RHS symbol so that `od_cfg_location_copy(@N)` never dereferences * garbage (notably for empty mid-rule actions and non-terminals) */ #define YYLLOC_DEFAULT(Current, Rhs, N) \ do { \ if (N) { \ (Current).first_line = (Rhs)[1].first_line; \ (Current).first_column = (Rhs)[1].first_column; \ (Current).last_line = (Rhs)[N].last_line; \ (Current).last_column = (Rhs)[N].last_column; \ (Current).first_offset = (Rhs)[1].first_offset; \ (Current).last_offset = (Rhs)[N].last_offset; \ (Current).filename = (Rhs)[1].filename; \ } else { \ (Current).first_line = (Current).last_line = \ (Rhs)[0].last_line; \ (Current).first_column = (Current).last_column = \ (Rhs)[0].last_column; \ (Current).first_offset = (Current).last_offset = \ (Rhs)[0].last_offset; \ (Current).filename = (Rhs)[0].filename; \ } \ } while (0) } %code requires { #include #include #include #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; #endif typedef struct od_cfg_parse_ctx od_cfg_parse_ctx_t; #ifndef YYLTYPE_IS_DECLARED #define YYLTYPE_IS_DECLARED 1 typedef od_cfg_location_t YYLTYPE; #endif } %code { /* bison 3.x generates yynerrs in pure parsers but doesn't use it */ #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #include #include #include #include #include int yylex(YYSTYPE *yylval, YYLTYPE *yylloc, yyscan_t scanner); static void yyerror(YYLTYPE *loc, yyscan_t scanner, od_cfg_parse_ctx_t *ctx, const char *msg) { (void)scanner; const char *hint = NULL; char hint_buf[256]; if (ctx->last_unknown_ident != NULL && strstr(msg, "IDENT") != NULL) { size_t ilen = strlen(ctx->last_unknown_ident); const char *sugg = od_cfg_keyword_suggest( ctx->last_unknown_ident, ilen, 2); if (sugg != NULL) { snprintf(hint_buf, sizeof(hint_buf), "unknown directive '%s'; did you mean '%s'?", ctx->last_unknown_ident, sugg); hint = hint_buf; } else { snprintf(hint_buf, sizeof(hint_buf), "unknown directive '%s'", ctx->last_unknown_ident); hint = hint_buf; } } if (hint != NULL) { od_cfg_diag_error_with_hint(ctx->diags, *loc, hint, "%s", msg); } else { od_cfg_diag_error(ctx->diags, *loc, "%s", msg); } } } %union { char *str; int64_t num; int boolean; struct { char *address; int conn_type; } selector; } %token ERROR_TOKEN %token IDENT %token STRING %token NUMBER %token SIZE_NUMBER %destructor { od_free($$); } %destructor { od_free($$.address); } %token YES "yes" %token NO "no" %token DAEMONIZE "daemonize" %token SEQUENTIAL_ROUTING "sequential_routing" %token ENABLE_ONLINE_RESTART "enable_online_restart" %token VIRTUAL_PROCESSING "virtual_processing" %token GRACEFUL_DIE_ON_ERRORS "graceful_die_on_errors" %token BINDWITH_REUSEPORT "bindwith_reuseport" %token ENABLE_HOST_WATCHER "enable_host_watcher" %token LOG_DEBUG "log_debug" %token LOG_TO_STDOUT "log_to_stdout" %token LOG_CONFIG "log_config" %token LOG_SESSION "log_session" %token LOG_QUERY "log_query" %token LOG_STATS "log_stats" %token LOG_ASYNC "log_async" %token LOG_SYSLOG "log_syslog" %token SMART_SEARCH_PATH_ENQUOTING "smart_search_path_enquoting" %token NODELAY "nodelay" %token DISABLE_NOLINGER "disable_nolinger" %token LOG_GENERAL_STATS_PROM "log_general_stats_prom" %token LOG_ROUTE_STATS_PROM "log_route_stats_prom" %token PID_FILE "pid_file" %token UNIX_SOCKET_DIR "unix_socket_dir" %token UNIX_SOCKET_MODE "unix_socket_mode" %token LOCKS_DIR "locks_dir" %token EXTERNAL_AUTH_SOCKET_PATH "external_auth_socket_path" %token AVAILABILITY_ZONE "availability_zone" %token LOG_FILE "log_file" %token LOG_FORMAT "log_format" %token LOG_SYSLOG_IDENT "log_syslog_ident" %token LOG_SYSLOG_FACILITY "log_syslog_facility" %token CPU_AFFINITY "cpu_affinity" %token HBA_FILE "hba_file" %token PRIORITY "priority" %token GRACEFUL_SHUTDOWN_TIMEOUT_MS "graceful_shutdown_timeout_ms" %token LOG_QUEUE_DEPTH "log_queue_depth" %token STATS_INTERVAL "stats_interval" %token CLIENT_MAX "client_max" %token CLIENT_MAX_ROUTING "client_max_routing" %token ACCEPT_RATE_LIMIT "accept_rate_limit" %token SERVER_LOGIN_RETRY "server_login_retry" %token READAHEAD "readahead" %token KEEPALIVE "keepalive" %token KEEPALIVE_KEEP_INTERVAL "keepalive_keep_interval" %token KEEPALIVE_PROBES "keepalive_probes" %token KEEPALIVE_USR_TIMEOUT "keepalive_usr_timeout" %token MAX_SIGTERMS_TO_DIE "max_sigterms_to_die" %token BACKEND_CONNECT_TIMEOUT_MS "backend_connect_timeout_ms" %token CANCEL_TIMEOUT_MS "cancel_timeout_ms" %token CANCEL_QUEUE_TIMEOUT_MS "cancel_queue_timeout_ms" %token CANCEL_MAX_INFLIGHT "cancel_max_inflight" %token CANCEL_RATE_LIMIT "cancel_rate_limit" %token RESOLVERS "resolvers" %token DNS_CACHE_TTL "dns_cache_ttl" %token CACHE_MSG_GC_SIZE "cache_msg_gc_size" %token CACHE_MSG_GC_COUNT "cache_msg_gc_count" %token CACHE_COROUTINE "cache_coroutine" %token COROUTINE_STACK_SIZE "coroutine_stack_size" %token SYSTEM_COROUTINE_STACK_SIZE "system_coroutine_stack_size" %token PROMHTTP_SERVER_PORT "promhttp_server_port" %token GROUP_CHECKER_INTERVAL "group_checker_interval" %token SHARED_POOL "shared_pool" %token POOL_SIZE "pool_size" %token CONN_DROP_OPTIONS "conn_drop_options" %token ONLINE_RESTART_DROP_OPTIONS "online_restart_drop_options" %token DROP_ENABLED "drop_enabled" %token RATE "rate" %token INTERVAL_MS "interval_ms" %token LISTEN "listen" %token HOST "host" %token PORT "port" %token TARGET_SESSION_ATTRS "target_session_attrs" %token CLIENT_LOGIN_TIMEOUT "client_login_timeout" %token BACKLOG "backlog" %token TLS "tls" %token TLS_CA_FILE "tls_ca_file" %token TLS_KEY_FILE "tls_key_file" %token TLS_CERT_FILE "tls_cert_file" %token TLS_PROTOCOLS "tls_protocols" %token CATCHUP_TIMEOUT "catchup_timeout" %token CATCHUP_CHECKS "catchup_checks" %token STORAGE "storage" %token TYPE "type" %token SERVER_MAX_ROUTING "server_max_routing" %token ENDPOINTS_STATUS_POLL_INTERVAL "endpoints_status_poll_interval" %token BALANCING "balancing" %token METHOD "method" %token SHOW_NOTICE_MESSAGES "show_notice_messages" %token COMPRESSION "compression" %token WORKERS "workers" %token PIPELINE "pipeline" %token CACHE "cache" %token CACHE_CHUNK "cache_chunk" %token PACKET_READ_SIZE "packet_read_size" %token PACKET_WRITE_QUEUE "packet_write_queue" %token SOFT_OOM "soft_oom" %token LIMIT "limit" %token PROCESS "process" %token CHECK_INTERVAL_MS "check_interval_ms" %token DROP "drop" %token SIGNAL "signal" %token MAX_RATE "max_rate" %token LDAP_ENDPOINT "ldap_endpoint" %token LDAP_SERVER "ldapserver" %token LDAP_PORT "ldapport" %token LDAP_PREFIX "ldapprefix" %token LDAP_SUFFIX "ldapsuffix" %token LDAP_BASEDN "ldapbasedn" %token LDAP_BINDDN "ldapbinddn" %token LDAP_BIND_PASSWD "ldapbindpasswd" %token LDAP_SEARCH_ATTRIBUTE "ldapsearchattribute" %token LDAP_SCHEME "ldapscheme" %token LDAP_SCOPE "ldapscope" %token LDAP_SEARCH_FILTER "ldapsearchfilter" %token DEFAULT "default" %token DATABASE "database" %token USER "user" %token LOCAL "local" %token HOSTSSL "hostssl" %token HOSTNOSSL "hostnossl" %token LDAP_STORAGE_USERNAME "ldap_storage_username" %token LDAP_STORAGE_PASSWORD "ldap_storage_password" %token AUTH_COMMON_NAME "auth_common_name" %token AUTHENTICATION "authentication" %token AUTH_MODULE "auth_module" %token ENABLE_MDB_IAMPROXY_AUTH "enable_mdb_iamproxy_auth" %token MDB_IAMPROXY_SOCKET_PATH "mdb_iamproxy_socket_path" %token AUTH_PAM_SERVICE "auth_pam_service" %token AUTH_QUERY "auth_query" %token AUTH_QUERY_DB "auth_query_db" %token AUTH_QUERY_USER "auth_query_user" %token PASSWORD_PASSTHROUGH "password_passthrough" %token PASSWORD "password" %token ROLE "role" %token CLIENT_FWD_ERROR "client_fwd_error" %token CLIENT_SHOW_ID "client_show_id" %token RESERVE_SESSION_SERVER_CONNECTION "reserve_session_server_connection" %token QUANTILES "quantiles" %token APPLICATION_NAME_ADD_HOST "application_name_add_host" %token SERVER_DROP_ON_CACHED_PLAN_ERROR "server_drop_on_cached_plan_error" %token SERVER_DROP_ON_OOM "server_drop_on_oom" %token SERVER_LIFETIME "server_lifetime" %token SERVER_PSTMT_CACHE_SIZE "server_pstmt_cache_size" %token LDAP_POOL_SIZE "ldap_pool_size" %token LDAP_POOL_TIMEOUT "ldap_pool_timeout" %token LDAP_POOL_TTL "ldap_pool_ttl" %token MAINTAIN_PARAMS "maintain_params" %token POOL "pool" %token POOL_ROUTING "pool_routing" %token MIN_POOL_SIZE "min_pool_size" %token POOL_TIMEOUT "pool_timeout" %token POOL_TTL "pool_ttl" %token POOL_DISCARD "pool_discard" %token POOL_SMART_DISCARD "pool_smart_discard" %token POOL_DISCARD_QUERY "pool_discard_query" %token POOL_CANCEL "pool_cancel" %token POOL_ROLLBACK "pool_rollback" %token POOL_RESET_TIMEOUT_MS "pool_reset_timeout_ms" %token POOL_RESERVE_PREPARED_STATEMENT "pool_reserve_prepared_statement" %token POOL_PIN_ON_LISTEN "pool_pin_on_listen" %token POOL_ATTACH_CHECK "pool_attach_check" %token POOL_ACQUIRE_FAIL_FAST "pool_acquire_fail_fast" %token POOL_NOTICE_AFTER_WAITING_MS "pool_notice_after_waiting_ms" %token POOL_CLIENT_IDLE_TIMEOUT "pool_client_idle_timeout" %token POOL_IDLE_IN_TRANSACTION_TIMEOUT "pool_idle_in_transaction_timeout" %token STORAGE_DATABASE "storage_db" %token STORAGE_USER "storage_user" %token STORAGE_PASSWORD "storage_password" %token LDAP_ENDPOINT_NAME "ldap_endpoint_name" %token LDAP_STORAGE_CREDENTIALS_ATTR "ldap_storage_credentials_attr" %token LDAP_STORAGE_CREDENTIALS "ldap_storage_credentials" %token WATCHDOG_LAG_QUERY "watchdog_lag_query" %token WATCHDOG_LAG_INTERVAL "watchdog_lag_interval" %token WATCHDOG "watchdog" %token OPTIONS "options" %token BACKEND_STARTUP_OPTIONS "backend_startup_options" %token GROUP_QUERY "group_query" %token GROUP_QUERY_USER "group_query_user" %token GROUP_QUERY_DB "group_query_db" %token GROUP "group" %token INCLUDE "include" %token AZ_AWARE "az_aware" %token VIRTUAL_TRANSACTION "virtual_transaction" %token QUERY_PARSING "query_parsing" %token MODE "mode" %token DISABLED "disabled" %token MINIMAL "minimal" %token FULL "full" %token MEM_LIMIT "mem_limit" %token RESERVED_CLIENTS "reserved_clients" %type bool_value %type string_value %type int_value %type size_value %type conn_type %type route_selector %% config: { /* * @$ is unset for this empty mid-rule action, so use the * parser context filename directly. Make an owning copy * since od_cfg_model_free() will od_cfg_location_free() it. * * only set it once (top-level config): included files share * the same model and would otherwise leak the previous * strdup by overwriting model->location */ if (ctx->include_depth == 0) { od_cfg_location_t loc = od_cfg_location_empty(ctx->filename); ctx->model->location = od_cfg_location_copy(loc); } } top_items ; top_items: %empty | top_items top_item ; top_item: DAEMONIZE bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.daemonize, $2, @1, "daemonize"); } | SEQUENTIAL_ROUTING bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.sequential_routing, $2, @1, "sequential_routing"); } | ENABLE_ONLINE_RESTART bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.enable_online_restart, $2, @1, "enable_online_restart"); } | VIRTUAL_PROCESSING bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.virtual_processing, $2, @1, "virtual_processing"); } | VIRTUAL_TRANSACTION bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.virtual_transaction, $2, @1, "virtual_transaction"); } | GRACEFUL_DIE_ON_ERRORS bool_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "graceful_die_on_errors"); } | BINDWITH_REUSEPORT bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.bindwith_reuseport, $2, @1, "bindwith_reuseport"); } | ENABLE_HOST_WATCHER bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.enable_host_watcher, $2, @1, "enable_host_watcher"); } | LOG_DEBUG bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_debug, $2, @1, "log_debug"); } | LOG_TO_STDOUT bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_to_stdout, $2, @1, "log_to_stdout"); } | LOG_CONFIG bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_config, $2, @1, "log_config"); } | LOG_SESSION bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_session, $2, @1, "log_session"); } | LOG_QUERY bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_query, $2, @1, "log_query"); } | LOG_STATS bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_stats, $2, @1, "log_stats"); } | LOG_ASYNC bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_async, $2, @1, "log_async"); } | LOG_SYSLOG bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_syslog, $2, @1, "log_syslog"); } | SMART_SEARCH_PATH_ENQUOTING bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.smart_search_path_enquoting, $2, @1, "smart_search_path_enquoting"); } | NODELAY bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.nodelay, $2, @1, "nodelay"); } | DISABLE_NOLINGER bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.disable_nolinger, $2, @1, "disable_nolinger"); } | LOG_GENERAL_STATS_PROM bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_general_stats_prom, $2, @1, "log_general_stats_prom"); } | LOG_ROUTE_STATS_PROM bool_value { od_cfg_set_bool(ctx->diags, &ctx->model->global.log_route_stats_prom, $2, @1, "log_route_stats_prom"); } | PID_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.pid_file, $2, @1, "pid_file"); $2 = NULL; } | UNIX_SOCKET_DIR string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.unix_socket_dir, $2, @1, "unix_socket_dir"); $2 = NULL; } | UNIX_SOCKET_MODE string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.unix_socket_mode, $2, @1, "unix_socket_mode"); $2 = NULL; } | LOCKS_DIR string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.locks_dir, $2, @1, "locks_dir"); $2 = NULL; } | EXTERNAL_AUTH_SOCKET_PATH string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.external_auth_socket_path, $2, @1, "external_auth_socket_path"); $2 = NULL; } | AVAILABILITY_ZONE string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.availability_zone, $2, @1, "availability_zone"); $2 = NULL; } | LOG_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.log_file, $2, @1, "log_file"); $2 = NULL; } | LOG_FORMAT string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.log_format, $2, @1, "log_format"); $2 = NULL; } | LOG_SYSLOG_IDENT string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.log_syslog_ident, $2, @1, "log_syslog_ident"); $2 = NULL; } | LOG_SYSLOG_FACILITY string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.log_syslog_facility, $2, @1, "log_syslog_facility"); $2 = NULL; } | CPU_AFFINITY string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.cpu_affinity, $2, @1, "cpu_affinity"); $2 = NULL; } | HBA_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->model->global.hba_file, $2, @1, "hba_file"); $2 = NULL; } | PRIORITY int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.priority, $2, @1, "priority"); } | GRACEFUL_SHUTDOWN_TIMEOUT_MS int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.graceful_shutdown_timeout_ms, $2, @1, "graceful_shutdown_timeout_ms"); } | LOG_QUEUE_DEPTH int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.log_queue_depth, $2, @1, "log_queue_depth"); } | STATS_INTERVAL int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.stats_interval, $2, @1, "stats_interval"); } | CLIENT_MAX int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.client_max, $2, @1, "client_max"); } | CLIENT_MAX_ROUTING int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.client_max_routing, $2, @1, "client_max_routing"); } | ACCEPT_RATE_LIMIT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->model->global.accept_rate_limit, $2, 1, INT_MAX, @1, "accept_rate_limit"); } | SERVER_LOGIN_RETRY int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.server_login_retry, $2, @1, "server_login_retry"); } | READAHEAD int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.readahead, $2, @1, "readahead"); } | KEEPALIVE int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.keepalive, $2, @1, "keepalive"); } | KEEPALIVE_KEEP_INTERVAL int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.keepalive_keep_interval, $2, @1, "keepalive_keep_interval"); } | KEEPALIVE_PROBES int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.keepalive_probes, $2, @1, "keepalive_probes"); } | KEEPALIVE_USR_TIMEOUT int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.keepalive_usr_timeout, $2, @1, "keepalive_usr_timeout"); } | MAX_SIGTERMS_TO_DIE int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.max_sigterms_to_die, $2, @1, "max_sigterms_to_die"); } | BACKEND_CONNECT_TIMEOUT_MS int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.backend_connect_timeout_ms, $2, @1, "backend_connect_timeout_ms"); } | CANCEL_TIMEOUT_MS int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.cancel_timeout_ms, $2, @1, "cancel_timeout_ms"); } | CANCEL_QUEUE_TIMEOUT_MS int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.cancel_queue_timeout_ms, $2, @1, "cancel_queue_timeout_ms"); } | CANCEL_MAX_INFLIGHT int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.cancel_max_inflight, $2, @1, "cancel_max_inflight"); } | CANCEL_RATE_LIMIT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->model->global.cancel_rate_limit, $2, 1, INT_MAX, @1, "cancel_rate_limit"); } | RESOLVERS int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->model->global.resolvers, $2, 1, INT_MAX, @1, "resolvers"); } | DNS_CACHE_TTL int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.dns_cache_ttl, $2, @1, "dns_cache_ttl"); } | CACHE_MSG_GC_SIZE int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.cache_msg_gc_size, $2, @1, "cache_msg_gc_size"); } | CACHE_MSG_GC_COUNT int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.cache_msg_gc_count, $2, @1, "cache_msg_gc_count"); } | CACHE_COROUTINE int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.cache_coroutine, $2, @1, "cache_coroutine"); } | COROUTINE_STACK_SIZE int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.coroutine_stack_size, $2, @1, "coroutine_stack_size"); } | SYSTEM_COROUTINE_STACK_SIZE int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.system_coroutine_stack_size, $2, @1, "system_coroutine_stack_size"); } | PROMHTTP_SERVER_PORT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->model->global.promhttp_server_port, $2, 1, 65535, @1, "promhttp_server_port"); } | GROUP_CHECKER_INTERVAL int_value { od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.group_checker_interval, $2, @1, "group_checker_interval"); } | WORKERS int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->model->global.workers, $2, 1, INT_MAX, @1, "workers"); } | WORKERS string_value { if (strcmp($2, "auto") != 0) { od_cfg_diag_error(ctx->diags, @1, "'%s' is invalid value for workers", $2); od_free($2); $2 = NULL; YYERROR; } od_cfg_set_int_from_i64(ctx->diags, &ctx->model->global.workers, (1 + od_get_ncpu()) / 2, @1, "workers"); od_free($2); $2 = NULL; } | PIPELINE int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "pipeline"); } | CACHE int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "cache"); } | CACHE_CHUNK int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "cache_chunk"); } | PACKET_READ_SIZE int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "packet_read_size"); } | PACKET_WRITE_QUEUE int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "packet_write_queue"); } | listen_section | storage_section | database_section | ldap_endpoint_section | shared_pool_section | conn_drop_options_section | soft_oom_section | query_parsing_section | INCLUDE STRING { if (!ctx->allow_include) { od_cfg_diag_error(ctx->diags, @1, "includes are forbidden in this context"); od_free($2); $2 = NULL; YYERROR; } int rc = od_cfg_parse_file_depth($2, ctx->model, ctx->diags, ctx->include_depth + 1, ctx->allow_include); od_free($2); if (rc != 0) { YYABORT; } } ; database_section: database_named | database_default ; database_named: DATABASE string_value { od_cfg_database_t *db = od_cfg_model_add_database(ctx->model, $2, 0 /* is_default */, @2, @1); if (db == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate database"); od_free($2); $2 = NULL; YYERROR; } $2 = NULL; ctx->current_database = db; } '{' database_items '}' { ctx->current_database = NULL; } ; database_default: DATABASE DEFAULT { od_cfg_database_t *db = od_cfg_model_add_database(ctx->model, NULL, 1 /* is_default */, @2, @1); if (db == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate database"); YYERROR; } ctx->current_database = db; } '{' database_items '}' { ctx->current_database = NULL; } ; database_items: %empty | database_items database_item ; database_item: user_route_item ; user_route_item: user_named | user_default | group ; group: GROUP string_value route_selector { ctx->current_group = od_cfg_database_add_group(ctx->current_database, $2, @1, @1, $3.address, @1, $3.conn_type, @1); if (ctx->current_group == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate group"); od_free($2); od_free($3.address); $2 = NULL; $3.address = NULL; YYERROR; } ctx->current_user = ctx->current_group; $2 = NULL; $3.address = NULL; } '{' route_items '}' { ctx->current_user = NULL; ctx->current_group = NULL; } user_default: USER DEFAULT route_selector { od_cfg_route_t *user = od_cfg_database_add_user(ctx->current_database, NULL, 1 /* is_default */, @1, @1, $3.address, @1, $3.conn_type, @1); if (user == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate user"); $3.address = NULL; YYERROR; } $3.address = NULL; ctx->current_user = user; } '{' route_items '}' { ctx->current_user = NULL; } ; user_named: USER string_value route_selector { od_cfg_route_t *user = od_cfg_database_add_user(ctx->current_database, $2, 0 /* is_default */, @1, @1, $3.address, @1, $3.conn_type, @1); if (user == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate user"); od_free($2); $2 = NULL; od_free($3.address); $3.address = NULL; YYERROR; } $2 = NULL; $3.address = NULL; ctx->current_user = user; } '{' route_items '}' { ctx->current_user = NULL; } ; conn_type: DEFAULT { $$ = OD_CFG_CONN_TYPE_DEFAULT; } | HOST { $$ = OD_CFG_CONN_TYPE_HOST; } | HOSTSSL { $$ = OD_CFG_CONN_TYPE_HOSTSSL; } | HOSTNOSSL { $$ = OD_CFG_CONN_TYPE_HOSTNOSSL; } | LOCAL { $$ = OD_CFG_CONN_TYPE_LOCAL; } ; /* * route_selector — optional address + optional conn_type in any order. * * Supported combinations (matching old parser behaviour): * * "addr" * conn_type * "addr" conn_type * conn_type "addr" */ route_selector: %empty { $$.address = NULL; $$.conn_type = OD_CFG_CONN_TYPE_DEFAULT; } | string_value { $$.address = $1; $1 = NULL; $$.conn_type = OD_CFG_CONN_TYPE_DEFAULT; } | conn_type { $$.address = NULL; $$.conn_type = $1; } | string_value conn_type { $$.address = $1; $1 = NULL; $$.conn_type = $2; } | conn_type string_value { $$.address = $2; $2 = NULL; $$.conn_type = $1; } ; route_items: %empty | route_items route_item ; route_item: AUTH_COMMON_NAME string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_rule_auth_common_name_t *cn = od_cfg_user_route_add_auth_common_name(ur, $2, 0 /* is_default */, @1, @1); if (cn == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate auth common name"); od_free($2); $2 = NULL; YYERROR; } $2 = NULL; } | AUTH_COMMON_NAME DEFAULT { od_cfg_route_t *ur = ctx->current_user; od_cfg_rule_auth_common_name_t *cn = od_cfg_user_route_add_auth_common_name(ur, NULL, 1 /* is_default */, @1, @1); if (cn == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate auth common name"); YYERROR; } } | AUTHENTICATION string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->authentication, $2, @1, "authentication"); $2 = NULL; } | AUTH_MODULE string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->auth_module, $2, @1, "auth_module"); $2 = NULL; } | ENABLE_MDB_IAMPROXY_AUTH bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->enable_mdb_iamproxy_auth, $2, @1, "enable_mdb_iamproxy_auth"); } | MDB_IAMPROXY_SOCKET_PATH string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->mdb_iamproxy_socket_path, $2, @1, "mdb_iamproxy_socket_path"); $2 = NULL; } | AUTH_PAM_SERVICE string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->auth_pam_service, $2, @1, "auth_pam_service"); $2 = NULL; } | TARGET_SESSION_ATTRS string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->target_session_attrs, $2, @1, "target_session_attrs"); $2 = NULL; } | AUTH_QUERY string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->auth_query, $2, @1, "auth_query"); $2 = NULL; } | AUTH_QUERY_DB string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->auth_query_db, $2, @1, "auth_query_db"); $2 = NULL; } | AUTH_QUERY_USER string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->auth_query_user, $2, @1, "auth_query_user"); $2 = NULL; } | PASSWORD_PASSTHROUGH bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->password_passthrough, $2, @1, "password_passthrough"); } | PASSWORD string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->password, $2, @1, "password"); $2 = NULL; } | ROLE string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->role, $2, @1, "role"); $2 = NULL; } | CLIENT_MAX int_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int_range_from_i64(ctx->diags, &ur->client_max, $2, 1, INT_MAX, @1, "client_max"); } | CLIENT_FWD_ERROR bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->client_fwd_error, $2, @1, "client_fwd_error"); } | CLIENT_SHOW_ID bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->client_show_id, $2, @1, "client_show_id"); } | RESERVE_SESSION_SERVER_CONNECTION bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->reserve_session_server_connection, $2, @1, "reserve_session_server_connection"); } | QUANTILES string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->quantiles, $2, @1, "quantiles"); $2 = NULL; } | APPLICATION_NAME_ADD_HOST bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->application_name_add_host, $2, @1, "application_name_add_host"); } | SERVER_DROP_ON_CACHED_PLAN_ERROR bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->server_drop_on_backend_plan_error, $2, @1, "server_drop_on_backend_plan_error"); } | SERVER_DROP_ON_OOM bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->server_drop_on_oom, $2, @1, "server_drop_on_oom"); } | SERVER_LIFETIME int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "server_lifetime must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_u64(ctx->diags, &ur->server_lifetime, (uint64_t)$2, @1, "server_lifetime"); } } | LDAP_POOL_SIZE int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "ldap_pool_size"); } | LDAP_POOL_TIMEOUT int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "ldap_pool_timeout"); } | LDAP_POOL_TTL int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "ldap_pool_ttl"); } | MAINTAIN_PARAMS bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->maintain_params, $2, @1, "maintain_params"); } | POOL string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->pool, $2, @1, "pool"); $2 = NULL; } | POOL_ROUTING string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->pool_routing, $2, @1, "pool_routing"); $2 = NULL; } | MIN_POOL_SIZE int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "min_pool_size must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->min_pool_size, (uint64_t)$2, @1, "min_pool_size"); } } | POOL_SIZE int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_size must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->pool_size, (uint64_t)$2, @1, "pool_size"); } } | POOL_TIMEOUT int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_timeout must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->pool_timeout, (uint64_t)$2, @1, "pool_timeout"); } } | POOL_TTL int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_ttl must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->pool_ttl, (uint64_t)$2, @1, "pool_ttl"); } } | POOL_DISCARD bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_discard, $2, @1, "pool_discard"); } | POOL_SMART_DISCARD bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_smart_discard, $2, @1, "pool_smart_discard"); } | POOL_DISCARD_QUERY string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->pool_discard_query, $2, @1, "pool_discard_query"); $2 = NULL; } | POOL_CANCEL bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_cancel, $2, @1, "pool_cancel"); } | POOL_ROLLBACK bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_rollback, $2, @1, "pool_rollback"); } | POOL_RESET_TIMEOUT_MS int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_reset_timeout_ms must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_u64(ctx->diags, &ur->pool_reset_timeout_ms, (uint64_t)$2, @1, "pool_reset_timeout_ms"); } } | POOL_RESERVE_PREPARED_STATEMENT bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_reserve_prepared_statement, $2, @1, "pool_reserve_prepared_statement"); } | SERVER_PSTMT_CACHE_SIZE int_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int_range_from_i64(ctx->diags, &ur->server_pstmt_cache_size, $2, 0, INT_MAX, @1, "server_pstmt_cache_size"); } | POOL_PIN_ON_LISTEN bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_pin_on_listen, $2, @1, "pool_pin_on_listen"); } | POOL_ATTACH_CHECK bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_attach_check, $2, @1, "pool_attach_check"); } | POOL_ACQUIRE_FAIL_FAST bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->pool_acquire_fail_fast, $2, @1, "pool_acquire_fail_fast"); } | POOL_NOTICE_AFTER_WAITING_MS int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_notice_after_waiting_ms must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_u64(ctx->diags, &ur->pool_notice_after_waiting_ms, (uint64_t)$2, @1, "pool_notice_after_waiting_ms"); } } | POOL_CLIENT_IDLE_TIMEOUT int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_client_idle_timeout must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_u64(ctx->diags, &ur->pool_client_idle_timeout, (uint64_t)$2, @1, "pool_client_idle_timeout"); } } | POOL_IDLE_IN_TRANSACTION_TIMEOUT int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "pool_idle_in_transaction_timeout must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_u64(ctx->diags, &ur->pool_idle_in_transaction_timeout, (uint64_t)$2, @1, "pool_idle_in_transaction_timeout"); } } | SHARED_POOL string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->shared_pool, $2, @1, "shared_pool"); $2 = NULL; } | STORAGE string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->storage, $2, @1, "storage"); $2 = NULL; } | STORAGE_DATABASE string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->storage_database, $2, @1, "storage_database"); $2 = NULL; } | STORAGE_USER string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->storage_user, $2, @1, "storage_user"); $2 = NULL; } | STORAGE_PASSWORD string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->storage_password, $2, @1, "storage_password"); $2 = NULL; } | LOG_DEBUG bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->log_debug, $2, @1, "log_debug"); } | LOG_QUERY bool_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_bool(ctx->diags, &ur->log_query, $2, @1, "log_query"); } | LDAP_ENDPOINT_NAME string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->ldap_endpoint_name, $2, @1, "ldap_endpoint_name"); $2 = NULL; } | LDAP_STORAGE_CREDENTIALS_ATTR string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_string(ctx->diags, &ur->ldap_storage_credentials_attr, $2, @1, "ldap_storage_credentials_attr"); $2 = NULL; } | LDAP_STORAGE_CREDENTIALS string_value { od_cfg_database_t *db = ctx->current_database; od_cfg_route_t *ur = ctx->current_user; od_cfg_ldap_storage_credentials_t *creds = od_cfg_user_route_add_ldap_creds(ur, @1, $2); if (creds == NULL) { od_cfg_diag_error(ctx->diags, @1, "can't allocate ldap storage credentials for %s.%s", db->name, ur->name); od_free($2); $2 = NULL; YYERROR; } $2 = NULL; ctx->current_ldap_creds = creds; } '{' ldap_storage_credentials_items '}' | WATCHDOG_LAG_QUERY string_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_database_t *db = ctx->current_database; if (ctx->current_watchdog == NULL) { od_cfg_diag_error(ctx->diags, @1, "watchdog_lag_query is not allowed outside watchdog, rule %s.%s", db->name, ur->name); od_free($2); $2 = NULL; YYERROR; } od_cfg_set_string(ctx->diags, &ur->watchdog_lag_query, $2, @1, "watchdog_lag_query"); $2 = NULL; } | WATCHDOG_LAG_INTERVAL int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "watchdog_lag_interval must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_database_t *db = ctx->current_database; if (ctx->current_watchdog == NULL) { od_cfg_diag_error(ctx->diags, @1, "watchdog_lag_interval is not allowed outside watchdog, rule %s.%s", db->name, ur->name); YYERROR; } od_cfg_set_int(ctx->diags, &ur->watchdog_lag_interval, (uint64_t)$2, @1, "watchdog_lag_interval"); } } | CATCHUP_TIMEOUT int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "catchup_timeout must be non-negative"); } else { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->catchup_timeout, (uint64_t)$2, @1, "catchup_timeout"); } } | CATCHUP_CHECKS int_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "catchup_checks"); } | GROUP_QUERY string_value { od_cfg_database_t *db = ctx->current_database; od_cfg_route_t *ur = ctx->current_user; if (ctx->current_group == NULL) { od_cfg_diag_error(ctx->diags, @1, "group_query is not allowed outside group, rule %s.%s", db->name, ur->name); YYERROR; } od_cfg_set_string(ctx->diags, &ur->group_query, $2, @1, "group_query"); $2 = NULL; } | GROUP_QUERY_USER string_value { od_cfg_database_t *db = ctx->current_database; od_cfg_route_t *ur = ctx->current_user; if (ctx->current_group == NULL) { od_cfg_diag_error(ctx->diags, @1, "group_query_user is not allowed outside group, rule %s.%s", db->name, ur->name); YYERROR; } od_cfg_set_string(ctx->diags, &ur->group_query_user, $2, @1, "group_query_user"); $2 = NULL; } | GROUP_QUERY_DB string_value { od_cfg_database_t *db = ctx->current_database; od_cfg_route_t *ur = ctx->current_user; if (ctx->current_group == NULL) { od_cfg_diag_error(ctx->diags, @1, "group_query_db is not allowed outside group, rule %s.%s", db->name, ur->name); YYERROR; } od_cfg_set_string(ctx->diags, &ur->group_query_db, $2, @1, "group_query_db"); $2 = NULL; } | OPTIONS { ctx->current_kvp_list = &ctx->current_user->pgoptions; } '{' kvp_items '}' { ctx->current_kvp_list = NULL; } | BACKEND_STARTUP_OPTIONS { ctx->current_kvp_list = &ctx->current_user->backend_startup_options; } '{' kvp_items '}' { ctx->current_kvp_list = NULL; } | KEEPALIVE int_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->keepalive, (uint64_t)$2, @1, "keepalive"); } | KEEPALIVE_KEEP_INTERVAL int_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->keepalive_keep_interval, (uint64_t)$2, @1, "keepalive_keep_interval"); } | KEEPALIVE_PROBES int_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->keepalive_probes, (uint64_t)$2, @1, "keepalive_probes"); } | KEEPALIVE_USR_TIMEOUT int_value { od_cfg_route_t *ur = ctx->current_user; od_cfg_set_int(ctx->diags, &ur->keepalive_usr_timeout, (uint64_t)$2, @1, "keepalive_usr_timeout"); } ; kvp_items: %empty | kvp_items string_value string_value { od_cfg_string_kvp_t *p = od_cfg_string_kvp_list_add(ctx->current_kvp_list, @1, $2, @1, $3, @1); if (p == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate key value pair"); od_free($2); od_free($3); $2 = NULL; $3 = NULL; YYERROR; } $2 = NULL; $3 = NULL; } ; ldap_storage_credentials_items: %empty | ldap_storage_credentials_items ldap_storage_credentials_item ; ldap_storage_credentials_item: LDAP_STORAGE_USERNAME string_value { od_cfg_ldap_storage_credentials_t *ur = ctx->current_ldap_creds; od_cfg_set_string(ctx->diags, &ur->ldap_storage_username, $2, @1, "ldap_storage_username"); $2 = NULL; } | LDAP_STORAGE_PASSWORD string_value { od_cfg_ldap_storage_credentials_t *ur = ctx->current_ldap_creds; od_cfg_set_string(ctx->diags, &ur->ldap_storage_password, $2, @1, "ldap_storage_password"); $2 = NULL; } ; ldap_endpoint_section: LDAP_ENDPOINT string_value { ctx->current_ldap_endpoint = od_cfg_model_add_ldap_endpoint(ctx->model, $2, @2, @1); if (ctx->current_ldap_endpoint == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate ldap_endpoint"); od_free($2); $2 = NULL; YYERROR; } $2 = NULL; } '{' ldap_endpoint_items '}' { ctx->current_ldap_endpoint = NULL; } ; ldap_endpoint_items: %empty | ldap_endpoint_items ldap_endpoint_item ; ldap_endpoint_item: LDAP_SERVER string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapserver, $2, @1, "ldapserver"); $2 = NULL; } | LDAP_PORT int_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "ldapport must be non-negative"); } else { od_cfg_set_u64(ctx->diags, &ctx->current_ldap_endpoint->ldapport, (uint64_t)$2, @1, "ldapport"); } } | LDAP_PREFIX string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapprefix, $2, @1, "ldapprefix"); $2 = NULL; } | LDAP_SUFFIX string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapsuffix, $2, @1, "ldapsuffix"); $2 = NULL; } | LDAP_SEARCH_ATTRIBUTE string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapsearchattribute, $2, @1, "ldapsearchattribute"); $2 = NULL; } | LDAP_SCOPE string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapscope, $2, @1, "ldapscope"); $2 = NULL; } | LDAP_SCHEME string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapscheme, $2, @1, "ldapscheme"); $2 = NULL; } | LDAP_BASEDN string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapbasedn, $2, @1, "ldapbasedn"); $2 = NULL; } | LDAP_BINDDN string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapbinddn, $2, @1, "ldapbinddn"); $2 = NULL; } | LDAP_BIND_PASSWD string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapbindpasswd, $2, @1, "ldapbindpasswd"); $2 = NULL; } | LDAP_SEARCH_FILTER string_value { od_cfg_set_string(ctx->diags, &ctx->current_ldap_endpoint->ldapsearchfilter, $2, @1, "ldapsearchfilter"); $2 = NULL; } ; storage_section: STORAGE string_value { ctx->current_storage = od_cfg_model_add_storage(ctx->model, $2, @2, @1); if (ctx->current_storage == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate storage section"); od_free($2); $2 = NULL; YYERROR; } $2 = NULL; } '{' storage_items '}' { ctx->current_storage = NULL; } ; storage_items: %empty | storage_items storage_item ; storage_item: TYPE string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->type, $2, @1, "type"); $2 = NULL; } | HOST string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->host, $2, @1, "host"); $2 = NULL; } | PORT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_storage->port, $2, 1, 65535, @1, "port"); } | TLS string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->tls, $2, @1, "tls"); $2 = NULL; } | TLS_CA_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->tls_ca_file, $2, @1, "tls_ca_file"); $2 = NULL; } | TLS_KEY_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->tls_key_file, $2, @1, "tls_key_file"); $2 = NULL; } | TLS_CERT_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->tls_cert_file, $2, @1, "tls_cert_file"); $2 = NULL; } | TLS_PROTOCOLS string_value { od_cfg_set_string(ctx->diags, &ctx->current_storage->tls_protocols, $2, @1, "tls_protocols"); $2 = NULL; } | SERVER_MAX_ROUTING int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_storage->server_max_routing, $2, 0, INT_MAX, @1, "server_max_routing"); } | ENDPOINTS_STATUS_POLL_INTERVAL int_value { od_cfg_set_int_range_from_i64( ctx->diags, &ctx->current_storage->endpoints_status_poll_interval_ms, $2, 1, INT_MAX, @1, "endpoints_status_poll_interval"); } | WATCHDOG { od_cfg_storage_t *s = ctx->current_storage; if (s->watchdog != NULL) { od_cfg_diag_error(ctx->diags, @1, "redefinition of watchdog for storage '%s'", s->name); YYERROR; } od_cfg_route_t *r = od_cfg_storage_add_watchdog(s, @1); if (r == NULL) { od_cfg_diag_error(ctx->diags, @1, "can't allocate watchdog for storage '%s'", s->name); YYERROR; } ctx->current_watchdog = r; ctx->current_user = r; } '{' route_items '}' { ctx->current_watchdog = NULL; ctx->current_user = NULL; } | storage_balancing_section ; storage_balancing_section: /* set current_balancing before entering balancing_section */ { ctx->current_balancing = &ctx->current_storage->balancing; } balancing_section ; balancing_section: BALANCING { od_cfg_balancing_t *b = ctx->current_balancing; if (b == NULL) { od_cfg_diag_error(ctx->diags, @1, "balancing section is not allowed here"); YYERROR; } if (b->seen.is_set) { od_cfg_diag_error(ctx->diags, @1, "balancing section is redefined"); YYERROR; } od_cfg_seen_set(&b->seen, @1); b->location = od_cfg_location_copy(@1); } '{' balancing_items '}' { ctx->current_balancing = NULL; } ; balancing_items: %empty | balancing_items balancing_item ; balancing_item: balancing_method_section | SHOW_NOTICE_MESSAGES bool_value { od_cfg_set_bool(ctx->diags, &ctx->current_balancing->show_notice_messages, $2, @1, "show_notice_messages"); } ; balancing_method_section: METHOD string_value { od_cfg_balancing_method_t *m = &ctx->current_balancing->method; if (m->seen.is_set) { od_cfg_diag_error(ctx->diags, @1, "balancing method is redefined"); od_free($2); $2 = NULL; YYERROR; } od_cfg_seen_set(&m->seen, @1); m->location = od_cfg_location_copy(@1); m->name = $2; $2 = NULL; ctx->current_balancing_method = m; } '{' balancing_method_items '}' { ctx->current_balancing_method = NULL; } ; balancing_method_items: %empty | balancing_method_items balancing_method_item ; balancing_method_item: AZ_AWARE bool_value { od_cfg_set_bool(ctx->diags, &ctx->current_balancing_method->az_aware, $2, @1, "az_aware"); } ; listen_section: LISTEN { ctx->current_listen = od_cfg_model_add_listen(ctx->model, @1); if (ctx->current_listen == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate listen section"); YYERROR; } } '{' listen_items '}' { ctx->current_listen = NULL; } ; listen_balancing_section: /* set current_balancing before entering balancing_section */ { ctx->current_balancing = &ctx->current_listen->balancing; } balancing_section ; listen_items: %empty | listen_items listen_item ; listen_item: HOST string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->host, $2, @1, "host"); $2 = NULL; } | PORT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_listen->port, $2, 1, 65535, @1, "port"); } | TARGET_SESSION_ATTRS string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->target_session_attrs, $2, @1, "target_session_attrs"); $2 = NULL; } | listen_balancing_section | CLIENT_LOGIN_TIMEOUT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_listen->client_login_timeout, $2, 0, INT_MAX, @1, "client_login_timeout"); } | BACKLOG int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_listen->backlog, $2, 0, INT_MAX, @1, "backlog"); } | TLS string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->tls, $2, @1, "tls"); $2 = NULL; } | TLS_CA_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->tls_ca_file, $2, @1, "tls_ca_file"); $2 = NULL; } | TLS_KEY_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->tls_key_file, $2, @1, "tls_key_file"); $2 = NULL; } | TLS_CERT_FILE string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->tls_cert_file, $2, @1, "tls_cert_file"); $2 = NULL; } | TLS_PROTOCOLS string_value { od_cfg_set_string(ctx->diags, &ctx->current_listen->tls_protocols, $2, @1, "tls_protocols"); $2 = NULL; } | CATCHUP_TIMEOUT int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_listen->catchup_timeout, $2, 0, INT_MAX, @1, "catchup_timeout"); } | COMPRESSION bool_value { od_cfg_diag_deprecated_ignored(ctx->diags, @1, "compression"); } | STORAGE string_value { od_cfg_listen_storage_t *s = od_cfg_listen_add_storage(ctx->current_listen, @1, $2); od_free($2); $2 = NULL; if (s == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate storage"); YYERROR; } } | RESERVED_CLIENTS int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_listen->reserved_clients, $2, 0, INT_MAX, @1, "reserved_clients"); } ; shared_pool_section: SHARED_POOL string_value { ctx->current_shared_pool = od_cfg_model_add_shared_pool(ctx->model, $2, @1, @1); if (ctx->current_shared_pool == NULL) { od_cfg_diag_error(ctx->diags, @1, "failed to allocate shared_pool"); od_free($2); $2 = NULL; YYERROR; } $2 = NULL; } '{' shared_pool_items '}' { ctx->current_shared_pool = NULL; } ; conn_drop_options_section: conn_drop_options_keyword { od_cfg_conn_drop_options_t *opts = &ctx->model->conn_drop_options; if (opts->seen.is_set) { od_cfg_diag_error(ctx->diags, @1, "conn_drop_options is redefined"); YYERROR; } od_cfg_seen_set(&opts->seen, @1); opts->location = od_cfg_location_copy(@1); ctx->current_conn_drop_options = opts; } '{' conn_drop_options_items '}' { ctx->current_conn_drop_options = NULL; } ; query_parsing_section: QUERY_PARSING { od_cfg_query_parsing_t *opts = &ctx->model->query_parsing; if (opts->seen.is_set) { od_cfg_diag_error(ctx->diags, @1, "query_parsing is redefined"); YYERROR; } od_cfg_seen_set(&opts->seen, @1); ctx->current_query_parsing = opts; } '{' query_parsing_items '}' { ctx->current_query_parsing = NULL; } query_parsing_items: %empty | query_parsing_items query_parsing_item ; query_parsing_item: MODE DISABLED { od_cfg_set_query_parsing_mode(ctx->diags, &ctx->current_query_parsing->mode, OD_CFG_QUERY_PARSING_MODE_DISABLED, @1, "mode"); } | MODE MINIMAL { od_cfg_set_query_parsing_mode(ctx->diags, &ctx->current_query_parsing->mode, OD_CFG_QUERY_PARSING_MODE_MINIMAL, @1, "mode"); } | MODE FULL { od_cfg_diag_error(ctx->diags, @1, "full mode of query parsing is not supported now"); YYERROR; } | MEM_LIMIT size_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "mem_limit must be non-negative"); } else { od_cfg_set_u64(ctx->diags, &ctx->current_query_parsing->mem_limit, $2, @1, "mem_limit"); } } ; soft_oom_section: SOFT_OOM { od_cfg_soft_oom_t *soom = &ctx->model->soft_oom; if (soom->seen.is_set) { od_cfg_diag_error(ctx->diags, @1, "soft_oom is redefined"); YYERROR; } od_cfg_seen_set(&soom->seen, @1); soom->location = od_cfg_location_copy(@1); ctx->current_soft_oom = soom; } '{' soft_oom_items '}' { ctx->current_soft_oom = NULL; } ; soft_oom_items: %empty | soft_oom_items soft_oom_item ; soft_oom_item: LIMIT size_value { if ($2 < 0) { od_cfg_diag_error(ctx->diags, @1, "limit must be non-negative"); } else { od_cfg_set_u64(ctx->diags, &ctx->current_soft_oom->limit, (uint64_t) $2, @1, "limit"); } } | PROCESS string_value { od_cfg_set_string(ctx->diags, &ctx->current_soft_oom->process, $2, @1, "process"); $2 = NULL; } | CHECK_INTERVAL_MS int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_soft_oom->check_interval_ms, $2, 0, INT_MAX, @1, "check_interval_ms"); } | soft_oom_drop ; soft_oom_drop: DROP { od_cfg_soft_oom_drop_t *drop = &ctx->current_soft_oom->drop; if (drop->seen.is_set) { od_cfg_diag_error(ctx->diags, @1, "soft_oom.drop is redefined"); YYERROR; } od_cfg_seen_set(&drop->seen, @1); drop->location = od_cfg_location_copy(@1); ctx->current_soft_oom_drop = drop; } '{' soft_oom_drop_items '}' { ctx->current_soft_oom_drop = NULL; } ; soft_oom_drop_items: %empty | soft_oom_drop_items soft_oom_drop_item ; soft_oom_drop_item: SIGNAL string_value { od_cfg_set_string(ctx->diags, &ctx->current_soft_oom_drop->signal, $2, @1, "signal"); $2 = NULL; } | MAX_RATE int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_soft_oom_drop->max_rate, $2, 0, INT_MAX, @1, "max_rate"); } ; conn_drop_options_keyword: CONN_DROP_OPTIONS | ONLINE_RESTART_DROP_OPTIONS ; conn_drop_options_items: %empty | conn_drop_options_items conn_drop_options_item ; conn_drop_options_item: DROP_ENABLED bool_value { od_cfg_set_bool(ctx->diags, &ctx->current_conn_drop_options->drop_enabled, $2, @1, "drop_enabled"); } | RATE int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_conn_drop_options->rate, $2, 1, INT_MAX, @1, "rate"); } | INTERVAL_MS int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_conn_drop_options->interval_ms, $2, 1, INT_MAX, @1, "interval_ms"); } ; shared_pool_items: %empty | shared_pool_items shared_pool_item ; shared_pool_item: POOL_SIZE int_value { od_cfg_set_int_range_from_i64(ctx->diags, &ctx->current_shared_pool->pool_size, $2, 0, INT_MAX, @1, "pool_size"); } ; bool_value: YES { $$ = 1; } | NO { $$ = 0; } ; string_value: STRING { $$ = $1; $1 = NULL; } ; int_value: NUMBER { $$ = $1; } ; size_value: SIZE_NUMBER { $$ = $1; } | NUMBER { $$ = $1; } ; %% odyssey-1.6.0-rc1/sources/cfg/reader.c000066400000000000000000000214311526064202200175300ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include static int read_file(const char *path, char **out, size_t *out_size, od_cfg_diag_list_t *diags) { FILE *file = fopen(path, "rb"); if (file == NULL) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to open config file '%s': %s", path, strerror(errno)); return NOT_OK_RESPONSE; } if (fseek(file, 0, SEEK_END) != 0) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to seek config file '%s': %s", path, strerror(errno)); fclose(file); return NOT_OK_RESPONSE; } long file_size = ftell(file); if (file_size < 0) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to tell config file '%s': %s", path, strerror(errno)); fclose(file); return NOT_OK_RESPONSE; } if (fseek(file, 0, SEEK_SET) != 0) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to seek config file '%s': %s", path, strerror(errno)); fclose(file); return NOT_OK_RESPONSE; } char *buf = od_malloc((size_t)file_size + 1); if (buf == NULL) { od_cfg_diag_error( diags, od_cfg_location_empty(path), "out of memory while reading config file '%s'", path); fclose(file); return NOT_OK_RESPONSE; } size_t read_size = fread(buf, 1, (size_t)file_size, file); if (read_size != (size_t)file_size) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to read config file '%s'", path); od_free(buf); fclose(file); return NOT_OK_RESPONSE; } if (fclose(file) != 0) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to close config file '%s': %s", path, strerror(errno)); od_free(buf); return NOT_OK_RESPONSE; } buf[file_size] = 0; *out = buf; *out_size = (size_t)file_size; return OK_RESPONSE; } #define OD_CFG_MAX_INCLUDE_DEPTH 16 #define OD_CFG_AUTOCONF_SUFFIX ".autoconf" #define MERGE_PLAIN_FLD(d, s, f) \ do { \ if ((s)->f.seen.is_set) { \ od_cfg_seen_free(&(d)->f.seen); \ (d)->f.value = (s)->f.value; \ (d)->f.seen = (s)->f.seen; \ (s)->f.seen.is_set = 0; \ (s)->f.seen.location.filename = NULL; \ } \ } while (0) #define MERGE_BOOL(d, s, f) MERGE_PLAIN_FLD(d, s, f) #define MERGE_INT(d, s, f) MERGE_PLAIN_FLD(d, s, f) #define MERGE_U64(d, s, f) MERGE_PLAIN_FLD(d, s, f) #define MERGE_STR(d, s, f) \ do { \ if ((s)->f.seen.is_set) { \ od_cfg_seen_free(&(d)->f.seen); \ od_free((d)->f.value); \ (d)->f.value = (s)->f.value; \ (d)->f.seen = (s)->f.seen; \ (s)->f.seen.is_set = 0; \ (s)->f.seen.location.filename = NULL; \ (s)->f.value = NULL; \ } \ } while (0) static void od_cfg_global_merge(od_cfg_global_t *dst, od_cfg_global_t *src) { MERGE_BOOL(dst, src, daemonize); MERGE_BOOL(dst, src, sequential_routing); MERGE_BOOL(dst, src, enable_online_restart); MERGE_BOOL(dst, src, virtual_processing); MERGE_BOOL(dst, src, virtual_transaction); MERGE_BOOL(dst, src, bindwith_reuseport); MERGE_BOOL(dst, src, enable_host_watcher); MERGE_BOOL(dst, src, log_debug); MERGE_BOOL(dst, src, log_to_stdout); MERGE_BOOL(dst, src, log_config); MERGE_BOOL(dst, src, log_session); MERGE_BOOL(dst, src, log_query); MERGE_BOOL(dst, src, log_stats); MERGE_BOOL(dst, src, log_async); MERGE_BOOL(dst, src, log_syslog); MERGE_BOOL(dst, src, smart_search_path_enquoting); MERGE_BOOL(dst, src, nodelay); MERGE_BOOL(dst, src, disable_nolinger); MERGE_BOOL(dst, src, log_general_stats_prom); MERGE_BOOL(dst, src, log_route_stats_prom); MERGE_STR(dst, src, pid_file); MERGE_STR(dst, src, unix_socket_dir); MERGE_STR(dst, src, unix_socket_mode); MERGE_STR(dst, src, locks_dir); MERGE_STR(dst, src, external_auth_socket_path); MERGE_STR(dst, src, availability_zone); MERGE_STR(dst, src, log_file); MERGE_STR(dst, src, log_format); MERGE_STR(dst, src, log_syslog_ident); MERGE_STR(dst, src, log_syslog_facility); MERGE_STR(dst, src, cpu_affinity); MERGE_STR(dst, src, hba_file); MERGE_INT(dst, src, priority); MERGE_INT(dst, src, graceful_shutdown_timeout_ms); MERGE_INT(dst, src, log_queue_depth); MERGE_INT(dst, src, stats_interval); MERGE_INT(dst, src, client_max); MERGE_INT(dst, src, client_max_routing); MERGE_INT(dst, src, accept_rate_limit); MERGE_INT(dst, src, server_login_retry); MERGE_INT(dst, src, readahead); MERGE_INT(dst, src, keepalive); MERGE_INT(dst, src, keepalive_keep_interval); MERGE_INT(dst, src, keepalive_probes); MERGE_INT(dst, src, keepalive_usr_timeout); MERGE_INT(dst, src, max_sigterms_to_die); MERGE_INT(dst, src, backend_connect_timeout_ms); MERGE_INT(dst, src, cancel_timeout_ms); MERGE_INT(dst, src, cancel_queue_timeout_ms); MERGE_INT(dst, src, cancel_max_inflight); MERGE_INT(dst, src, resolvers); MERGE_INT(dst, src, dns_cache_ttl); MERGE_INT(dst, src, cache_msg_gc_size); MERGE_INT(dst, src, cache_msg_gc_count); MERGE_INT(dst, src, cache_coroutine); MERGE_INT(dst, src, coroutine_stack_size); MERGE_INT(dst, src, system_coroutine_stack_size); MERGE_INT(dst, src, promhttp_server_port); MERGE_INT(dst, src, group_checker_interval); MERGE_INT(dst, src, workers); #undef MERGE_PLAIN_FLD #undef MERGE_BOOL #undef MERGE_INT #undef MERGE_U64 #undef MERGE_STR } static int od_cfg_try_autoconf(const char *config_path, od_cfg_model_t *model, od_cfg_diag_list_t *diags) { size_t path_len = strlen(config_path); size_t autoconf_path_len = path_len + strlen(OD_CFG_AUTOCONF_SUFFIX) + 1; char *autoconf_path = od_malloc(autoconf_path_len); if (autoconf_path == NULL) { od_cfg_diag_error(diags, od_cfg_location_empty(config_path), "out of memory while building autoconf path"); return -1; } od_snprintf(autoconf_path, autoconf_path_len, "%s%s", config_path, OD_CFG_AUTOCONF_SUFFIX); if (access(autoconf_path, F_OK) != 0) { od_free(autoconf_path); return 0; } /* swap in a fresh global so autoconf can set fields without duplicate errors */ od_cfg_global_t saved_global = model->global; memset(&model->global, 0, sizeof(model->global)); int rc = od_cfg_parse_file_depth(autoconf_path, model, diags, 1, 0); od_free(autoconf_path); if (rc != 0) { /* autoconf failed */ od_cfg_global_free(&model->global); model->global = saved_global; return -1; } /* autoconf values override base values */ od_cfg_global_merge(&saved_global, &model->global); /* free the scratch global (string values already moved) */ od_cfg_global_free(&model->global); model->global = saved_global; return 0; } int od_cfg_parse_file_depth(const char *path, od_cfg_model_t *model, od_cfg_diag_list_t *diags, int depth, int allow_include) { if (depth > OD_CFG_MAX_INCLUDE_DEPTH) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "include depth limit (%d) exceeded — " "possible circular include", OD_CFG_MAX_INCLUDE_DEPTH); return -1; } char *buf = NULL; size_t size = 0; int rc = read_file(path, &buf, &size, diags); if (rc != 0) { return -1; } od_cfg_parse_ctx_t ctx; od_cfg_parse_ctx_init(&ctx, path, buf, size, model, diags); ctx.include_depth = depth; ctx.allow_include = allow_include; yyscan_t scanner; if (yylex_init_extra(&ctx, &scanner) != 0) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to initialize config scanner"); od_cfg_parse_ctx_free(&ctx); od_free(buf); return -1; } YY_BUFFER_STATE buffer = yy_scan_bytes(buf, (int)size, scanner); if (buffer == NULL) { od_cfg_diag_error(diags, od_cfg_location_empty(path), "failed to create config scanner buffer"); yylex_destroy(scanner); od_cfg_parse_ctx_free(&ctx); od_free(buf); return -1; } int parse_rc = yyparse(scanner, &ctx); yy_delete_buffer(buffer, scanner); yylex_destroy(scanner); od_cfg_parse_ctx_free(&ctx); od_free(buf); if (parse_rc != 0 || od_cfg_diag_has_errors(diags)) { return -1; } /* validate only at top level, not for each included file */ if (depth == 0) { /* include autoconf, override all previous options */ rc = od_cfg_try_autoconf(path, model, diags); if (rc != 0) { return -1; } return od_cfg_validate_model(model, diags); } return 0; } int od_cfg_parse_file(const char *path, od_cfg_model_t *model, od_cfg_diag_list_t *diags) { return od_cfg_parse_file_depth(path, model, diags, 0, 1); } odyssey-1.6.0-rc1/sources/cfg/scan.l000066400000000000000000000112241526064202200172220ustar00rootroot00000000000000%option reentrant %option bison-bridge %option bison-locations %option noyywrap %option nodefault %option nounput %option noinput %option extra-type="od_cfg_parse_ctx_t *" %top{ /* flex-generated code triggers sign-compare warnings — suppress for this TU */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" #include #include #include #include } %{ static char * od_cfg_lex_strdup(od_cfg_parse_ctx_t *ctx, const char *s, size_t len) { (void)ctx; char *copy = od_malloc(len + 1); if (copy == NULL) { return NULL; } memcpy(copy, s, len); copy[len] = 0; return copy; } static char * od_cfg_unescape_string(od_cfg_parse_ctx_t *ctx, const char *s, size_t len, od_cfg_location_t loc) { char *out = od_malloc(len + 1); if (out == NULL) { od_cfg_diag_error(ctx->diags, loc, "out of memory"); return NULL; } size_t j = 0; for (size_t i = 0; i < len; i++) { if (s[i] == '\\' && i + 1 < len && s[i + 1] == '"') { out[j++] = '"'; i++; continue; } out[j++] = s[i]; } out[j] = 0; return out; } #undef YY_USER_ACTION #define YY_USER_ACTION \ do { \ yylloc->filename = yyextra->filename; \ yylloc->first_line = yyextra->lexer_line; \ yylloc->first_column = yyextra->lexer_column; \ yylloc->first_offset = yyextra->lexer_offset; \ \ for (size_t i = 0; i < (size_t)yyleng; i++) { \ if (yytext[i] == '\n') { \ yyextra->lexer_line++; \ yyextra->lexer_column = 1; \ } else { \ yyextra->lexer_column++; \ } \ yyextra->lexer_offset++; \ } \ \ yylloc->last_line = yyextra->lexer_line; \ yylloc->last_column = yyextra->lexer_column; \ yylloc->last_offset = yyextra->lexer_offset; \ } while (0); %} DIGIT [0-9] IDENT_START [A-Za-z_] IDENT_CONT [A-Za-z0-9_] IDENT {IDENT_START}{IDENT_CONT}* NUMBER_SIGNED -?{DIGIT}+ SIZE_SUFFIX [kK][bB]?|[mM][bB]?|[gG][bB]?|[bB] %% [ \t\r\n]+ ; "#".* ; "{" return '{'; "}" return '}'; {NUMBER_SIGNED}{SIZE_SUFFIX} { errno = 0; char *suffix = NULL; int64_t v = strtoll(yytext, &suffix, 10); if (errno == ERANGE) { od_cfg_diag_error(yyextra->diags, *yylloc, "number is out of range"); return ERROR_TOKEN; } int64_t mult = 1; switch (*suffix) { case 'k': case 'K': mult = 1024LL; break; case 'm': case 'M': mult = 1024LL * 1024; break; case 'g': case 'G': mult = 1024LL * 1024 * 1024; break; case 'b': case 'B': mult = 1; break; default: break; } yylval->num = v * mult; return SIZE_NUMBER; } {NUMBER_SIGNED} { errno = 0; int64_t v = strtoll(yytext, NULL, 10); if (errno == ERANGE) { od_cfg_diag_error(yyextra->diags, *yylloc, "number is out of range"); return ERROR_TOKEN; } yylval->num = v; return NUMBER; } \"([^\"]|\\\")*\" { assert(yyleng >= 2); size_t len = (size_t)yyleng - 2; char *copy = od_cfg_unescape_string(yyextra, yytext + 1, len, *yylloc); if (copy == NULL) { return ERROR_TOKEN; } yylval->str = copy; return STRING; } {IDENT} { int tok = od_cfg_keyword_lookup(yytext, (size_t)yyleng); if (tok != 0) { return tok; } char *copy = od_cfg_lex_strdup(yyextra, yytext, (size_t)yyleng); if (copy == NULL) { od_cfg_diag_error(yyextra->diags, *yylloc, "out of memory"); return ERROR_TOKEN; } od_free(yyextra->last_unknown_ident); yyextra->last_unknown_ident = od_cfg_lex_strdup(yyextra, yytext, (size_t)yyleng); if (yyextra->last_unknown_ident == NULL) { od_cfg_diag_error(yyextra->diags, *yylloc, "out of memory"); od_free(copy); return ERROR_TOKEN; } yylval->str = copy; return IDENT; } . { od_cfg_diag_error(yyextra->diags, *yylloc, "unexpected character '%c'", yytext[0]); return ERROR_TOKEN; } %% odyssey-1.6.0-rc1/sources/client.c000066400000000000000000000054131526064202200170070ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include void od_client_init(od_client_t *client) { client->state = OD_CLIENT_UNDEF; client->type = OD_POOL_CLIENT_EXTERNAL; client->coroutine_id = 0; client->tls = NULL; client->rule = NULL; client->source = NULL; client->server = NULL; client->route = NULL; client->global = NULL; client->time_accept = 0; client->time_setup = 0; client->time_queue_start = 0; #ifdef LDAP_FOUND client->ldap_storage_username = NULL; client->ldap_storage_username_len = 0; client->ldap_storage_password = NULL; client->ldap_storage_password_len = 0; client->ldap_auth_dn = NULL; #endif client->external_id = NULL; client->physical_rep = false; client->logical_rep = false; kiwi_be_startup_init(&client->startup); kiwi_vars_init(&client->vars); kiwi_key_init(&client->key); od_io_init(&client->io); kiwi_password_init(&client->password); kiwi_password_init(&client->received_password); od_list_init(&client->link_pool); od_list_init(&client->link); client->prep_stmt_ids = NULL; client->portals = NULL; client->last_catchup_lag = 0; atomic_store(&client->killed, 0); od_relay_init(&client->relay, client); memset(client->peer, 0, sizeof(client->peer)); od_query_ctx_reset(&client->query_ctx); client->pending_begin = 0; memset(client->scram_client_key, 0, sizeof(client->scram_client_key)); memset(client->scram_server_key, 0, sizeof(client->scram_server_key)); client->scram_key_valid = 0; client->read_msg = NULL; /* Init GUCs. */ client->backend_pin = false; client->opportunistic_acquire = false; } void od_client_free(od_client_t *client) { #ifdef LDAP_FOUND od_free(client->ldap_auth_dn); #endif if (client->rule != NULL) { od_rules_unref(client->rule); } od_relay_destroy(&client->relay); od_io_free(&client->io); /* clear password if saved any */ kiwi_password_free(&client->password); kiwi_password_free(&client->received_password); if (client->prep_stmt_ids) { od_client_pstmt_hashmap_free(client->prep_stmt_ids); } if (client->portals) { od_client_portal_hashmap_free(client->portals); } if (client->external_id) { od_free(client->external_id); } memset(client->scram_client_key, 0, sizeof(client->scram_client_key)); memset(client->scram_server_key, 0, sizeof(client->scram_server_key)); client->scram_key_valid = 0; machine_msg_free_safe(client->read_msg); od_free(client); } uint32_t od_client_login_timeout(const od_client_t *client) { if (client->source != NULL) { return (uint32_t)client->source->config->client_login_timeout; } /* TODO: do not use infinite timeout */ return UINT32_MAX; } odyssey-1.6.0-rc1/sources/common/000077500000000000000000000000001526064202200166525ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/common/base64.c000066400000000000000000000113231526064202200201020ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * base64.c * Encoding and decoding routines for base64 without whitespace. * * Copyright (c) 2001-2025, PostgreSQL Global Development Group * * * IDENTIFICATION * src/common/base64.c * *------------------------------------------------------------------------- */ #include "od_c.h" #include "pg_compat.h" #include "common/base64.h" /* * BASE64 */ static const char _base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const int8 b64lookup[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, }; /* * pg_b64_encode * * Encode the 'src' byte array into base64. Returns the length of the encoded * string, and -1 in the event of an error with the result buffer zeroed for * safety. */ int pg_b64_encode(const uint8 *src, int len, char *dst, int dstlen) { char *p; const uint8 *s, *end = src + len; int pos = 2; uint32 buf = 0; s = src; p = dst; while (s < end) { buf |= *s << (pos << 3); pos--; s++; /* write it out */ if (pos < 0) { /* * Leave if there is an overflow in the area allocated for the * encoded string. */ if ((p - dst + 4) > dstlen) { goto error; } *p++ = _base64[(buf >> 18) & 0x3f]; *p++ = _base64[(buf >> 12) & 0x3f]; *p++ = _base64[(buf >> 6) & 0x3f]; *p++ = _base64[buf & 0x3f]; pos = 2; buf = 0; } } if (pos != 2) { /* * Leave if there is an overflow in the area allocated for the encoded * string. */ if ((p - dst + 4) > dstlen) { goto error; } *p++ = _base64[(buf >> 18) & 0x3f]; *p++ = _base64[(buf >> 12) & 0x3f]; *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '='; *p++ = '='; } Assert((p - dst) <= dstlen); return p - dst; error: memset(dst, 0, dstlen); return -1; } /* * pg_b64_decode * * Decode the given base64 string. Returns the length of the decoded * string on success, and -1 in the event of an error with the result * buffer zeroed for safety. */ int pg_b64_decode(const char *src, int len, uint8 *dst, int dstlen) { const char *srcend = src + len, *s = src; uint8 *p = dst; char c; int b = 0; uint32 buf = 0; int pos = 0, end = 0; while (s < srcend) { c = *s++; /* Leave if a whitespace is found */ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { goto error; } if (c == '=') { /* end sequence */ if (!end) { if (pos == 2) { end = 1; } else if (pos == 3) { end = 2; } else { /* * Unexpected "=" character found while decoding base64 * sequence. */ goto error; } } b = 0; } else { b = -1; if (c > 0 && c < 127) { b = b64lookup[(unsigned char)c]; } if (b < 0) { /* invalid symbol found */ goto error; } } /* add it to buffer */ buf = (buf << 6) + b; pos++; if (pos == 4) { /* * Leave if there is an overflow in the area allocated for the * decoded string. */ if ((p - dst + 1) > dstlen) { goto error; } *p++ = (buf >> 16) & 255; if (end == 0 || end > 1) { /* overflow check */ if ((p - dst + 1) > dstlen) { goto error; } *p++ = (buf >> 8) & 255; } if (end == 0 || end > 2) { /* overflow check */ if ((p - dst + 1) > dstlen) { goto error; } *p++ = buf & 255; } buf = 0; pos = 0; } } if (pos != 0) { /* * base64 end sequence is invalid. Input data is missing padding, is * truncated or is otherwise corrupted. */ goto error; } Assert((p - dst) <= dstlen); return p - dst; error: memset(dst, 0, dstlen); return -1; } /* * pg_b64_enc_len * * Returns to caller the length of the string if it were encoded with * base64 based on the length provided by caller. This is useful to * estimate how large a buffer allocation needs to be done before doing * the actual encoding. */ int pg_b64_enc_len(int srclen) { /* 3 bytes will be converted to 4 */ return (srclen + 2) / 3 * 4; } /* * pg_b64_dec_len * * Returns to caller the length of the string if it were to be decoded * with base64, based on the length given by caller. This is useful to * estimate how large a buffer allocation needs to be done before doing * the actual decoding. */ int pg_b64_dec_len(int srclen) { return (srclen * 3) >> 2; } odyssey-1.6.0-rc1/sources/common/cryptohash_openssl.c000066400000000000000000000205451526064202200227530ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * cryptohash_openssl.c * Set of wrapper routines on top of OpenSSL to support cryptographic * hash functions. * * This should only be used if code is compiled with OpenSSL support. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/common/cryptohash_openssl.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #include "postgres.h" #else #include "postgres_fe.h" #endif #include #include #include "common/cryptohash.h" #include "common/md5.h" #include "common/sha1.h" #include "common/sha2.h" #ifndef FRONTEND #include "utils/memutils.h" #include "utils/resowner.h" #endif /* * In the backend, use an allocation in TopMemoryContext to count for * resowner cleanup handling. In the frontend, use malloc to be able * to return a failure status back to the caller. */ #ifndef FRONTEND #define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size) #define FREE(ptr) pfree(ptr) #else #define ALLOC(size) od_malloc(size) #define FREE(ptr) od_free(ptr) #endif /* Set of error states */ typedef enum pg_cryptohash_errno { PG_CRYPTOHASH_ERROR_NONE = 0, PG_CRYPTOHASH_ERROR_DEST_LEN, PG_CRYPTOHASH_ERROR_OPENSSL, } pg_cryptohash_errno; /* * Internal pg_cryptohash_ctx structure. * * This tracks the resource owner associated to each EVP context data * for the backend. */ struct pg_cryptohash_ctx { pg_cryptohash_type type; pg_cryptohash_errno error; const char *errreason; EVP_MD_CTX *evpctx; #ifndef FRONTEND ResourceOwner resowner; #endif }; /* ResourceOwner callbacks to hold cryptohash contexts */ #ifndef FRONTEND static void ResOwnerReleaseCryptoHash(Datum res); static const ResourceOwnerDesc cryptohash_resowner_desc = { .name = "OpenSSL cryptohash context", .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS, .release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS, .ReleaseResource = ResOwnerReleaseCryptoHash, .DebugPrint = NULL /* the default message is fine */ }; /* Convenience wrappers over ResourceOwnerRemember/Forget */ static inline void ResourceOwnerRememberCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx) { ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc); } static inline void ResourceOwnerForgetCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx) { ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc); } #endif static const char *SSLerrmessage(unsigned long ecode) { if (ecode == 0) { return NULL; } /* * This may return NULL, but we would fall back to a default error path if * that were the case. */ return ERR_reason_error_string(ecode); } /* * pg_cryptohash_create * * Allocate a hash context. Returns NULL on failure for an OOM. The * backend issues an error, without returning. */ pg_cryptohash_ctx *pg_cryptohash_create(pg_cryptohash_type type) { pg_cryptohash_ctx *ctx; /* * Make sure that the resource owner has space to remember this reference. * This can error out with "out of memory", so do this before any other * allocation to avoid leaking. */ #ifndef FRONTEND ResourceOwnerEnlarge(CurrentResourceOwner); #endif ctx = ALLOC(sizeof(pg_cryptohash_ctx)); if (ctx == NULL) { return NULL; } memset(ctx, 0, sizeof(pg_cryptohash_ctx)); ctx->type = type; ctx->error = PG_CRYPTOHASH_ERROR_NONE; ctx->errreason = NULL; /* * Initialization takes care of assigning the correct type for OpenSSL. * Also ensure that there aren't any unconsumed errors in the queue from * previous runs. */ ERR_clear_error(); ctx->evpctx = EVP_MD_CTX_create(); if (ctx->evpctx == NULL) { explicit_bzero(ctx, sizeof(pg_cryptohash_ctx)); FREE(ctx); #ifndef FRONTEND ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #else return NULL; #endif } #ifndef FRONTEND ctx->resowner = CurrentResourceOwner; ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx); #endif return ctx; } /* * pg_cryptohash_init * * Initialize a hash context. Returns 0 on success, and -1 on failure. */ int pg_cryptohash_init(pg_cryptohash_ctx *ctx) { int status = 0; if (ctx == NULL) { return -1; } switch (ctx->type) { case PG_MD5: status = EVP_DigestInit_ex(ctx->evpctx, EVP_md5(), NULL); break; case PG_SHA1: status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha1(), NULL); break; case PG_SHA224: status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha224(), NULL); break; case PG_SHA256: status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha256(), NULL); break; case PG_SHA384: status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha384(), NULL); break; case PG_SHA512: status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha512(), NULL); break; } /* OpenSSL internals return 1 on success, 0 on failure */ if (status <= 0) { ctx->errreason = SSLerrmessage(ERR_get_error()); ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL; /* * The OpenSSL error queue should normally be empty since we've * consumed an error, but cipher initialization can in FIPS-enabled * OpenSSL builds generate two errors so clear the queue here as well. */ ERR_clear_error(); return -1; } return 0; } /* * pg_cryptohash_update * * Update a hash context. Returns 0 on success, and -1 on failure. */ int pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len) { int status = 0; if (ctx == NULL) { return -1; } status = EVP_DigestUpdate(ctx->evpctx, data, len); /* OpenSSL internals return 1 on success, 0 on failure */ if (status <= 0) { ctx->errreason = SSLerrmessage(ERR_get_error()); ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL; return -1; } return 0; } /* * pg_cryptohash_final * * Finalize a hash context. Returns 0 on success, and -1 on failure. */ int pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len) { int status = 0; if (ctx == NULL) { return -1; } switch (ctx->type) { case PG_MD5: if (len < MD5_DIGEST_LENGTH) { ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN; return -1; } break; case PG_SHA1: if (len < SHA1_DIGEST_LENGTH) { ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN; return -1; } break; case PG_SHA224: if (len < PG_SHA224_DIGEST_LENGTH) { ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN; return -1; } break; case PG_SHA256: if (len < PG_SHA256_DIGEST_LENGTH) { ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN; return -1; } break; case PG_SHA384: if (len < PG_SHA384_DIGEST_LENGTH) { ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN; return -1; } break; case PG_SHA512: if (len < PG_SHA512_DIGEST_LENGTH) { ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN; return -1; } break; } status = EVP_DigestFinal_ex(ctx->evpctx, dest, 0); /* OpenSSL internals return 1 on success, 0 on failure */ if (status <= 0) { ctx->errreason = SSLerrmessage(ERR_get_error()); ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL; return -1; } return 0; } /* * pg_cryptohash_free * * Free a hash context. */ void pg_cryptohash_free(pg_cryptohash_ctx *ctx) { if (ctx == NULL) { return; } EVP_MD_CTX_destroy(ctx->evpctx); #ifndef FRONTEND if (ctx->resowner) { ResourceOwnerForgetCryptoHash(ctx->resowner, ctx); } #endif explicit_bzero(ctx, sizeof(pg_cryptohash_ctx)); FREE(ctx); } /* * pg_cryptohash_error * * Returns a static string providing details about an error that * happened during a computation. */ const char *pg_cryptohash_error(pg_cryptohash_ctx *ctx) { /* * This implementation would never fail because of an out-of-memory error, * except when creating the context. */ if (ctx == NULL) { return _("out of memory"); } /* * If a reason is provided, rely on it, else fallback to any error code * set. */ if (ctx->errreason) { return ctx->errreason; } switch (ctx->error) { case PG_CRYPTOHASH_ERROR_NONE: return _("success"); case PG_CRYPTOHASH_ERROR_DEST_LEN: return _("destination buffer too small"); case PG_CRYPTOHASH_ERROR_OPENSSL: return _("OpenSSL failure"); } Assert(false); /* cannot be reached */ return _("success"); } /* ResourceOwner callbacks */ #ifndef FRONTEND static void ResOwnerReleaseCryptoHash(Datum res) { pg_cryptohash_ctx *ctx = (pg_cryptohash_ctx *)DatumGetPointer(res); ctx->resowner = NULL; pg_cryptohash_free(ctx); } #endif odyssey-1.6.0-rc1/sources/common/fe_memutils.c000066400000000000000000000053421526064202200213330ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * fe_memutils.c * memory management support for frontend code * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/common/fe_memutils.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #error "This file is not expected to be compiled for backend code" #endif #include "postgres_fe.h" static inline void *pg_malloc_internal(size_t size, int flags) { void *tmp; /* Avoid unportable behavior of malloc(0) */ if (size == 0) { size = 1; } tmp = od_malloc(size); if (tmp == NULL) { if ((flags & MCXT_ALLOC_NO_OOM) == 0) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } return NULL; } if ((flags & MCXT_ALLOC_ZERO) != 0) { MemSet(tmp, 0, size); } return tmp; } void *pg_malloc(size_t size) { return pg_malloc_internal(size, 0); } void *pg_malloc0(size_t size) { return pg_malloc_internal(size, MCXT_ALLOC_ZERO); } void *pg_malloc_extended(size_t size, int flags) { return pg_malloc_internal(size, flags); } void *pg_realloc(void *ptr, size_t size) { void *tmp; /* Avoid unportable behavior of realloc(NULL, 0) */ if (ptr == NULL && size == 0) { size = 1; } tmp = od_realloc(ptr, size); if (!tmp) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } return tmp; } /* * "Safe" wrapper around strdup(). */ char *pg_strdup(const char *in) { char *tmp; if (!in) { fprintf(stderr, _("cannot duplicate null pointer (internal error)\n")); exit(EXIT_FAILURE); } tmp = od_strdup(in); if (!tmp) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } return tmp; } void pg_free(void *ptr) { od_free(ptr); } /* * Frontend emulation of backend memory management functions. Useful for * programs that compile backend files. */ void *palloc(Size size) { return pg_malloc_internal(size, 0); } void *palloc0(Size size) { return pg_malloc_internal(size, MCXT_ALLOC_ZERO); } void *palloc_extended(Size size, int flags) { return pg_malloc_internal(size, flags); } void pfree(void *pointer) { pg_free(pointer); } char *pstrdup(const char *in) { return pg_strdup(in); } char *pnstrdup(const char *in, Size size) { char *tmp; int len; if (!in) { fprintf(stderr, _("cannot duplicate null pointer (internal error)\n")); exit(EXIT_FAILURE); } len = strnlen(in, size); tmp = od_malloc(len + 1); if (tmp == NULL) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } memcpy(tmp, in, len); tmp[len] = '\0'; return tmp; } void *repalloc(void *pointer, Size size) { return pg_realloc(pointer, size); } odyssey-1.6.0-rc1/sources/common/hmac.c000066400000000000000000000155761526064202200177440ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * hmac.c * Implements Keyed-Hashing for Message Authentication (HMAC) * * Fallback implementation of HMAC, as specified in RFC 2104. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/common/hmac.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #include "postgres.h" #else #include "postgres_fe.h" #endif #include "od_c.h" #include "pg_compat.h" #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wsign-compare" #endif #include "common/cryptohash.h" #include "common/hmac.h" #include "common/md5.h" #include "common/sha1.h" #include "common/sha2.h" /* * In backend, use palloc/pfree to ease the error handling. In frontend, * use malloc to be able to return a failure status back to the caller. */ #ifndef FRONTEND #define ALLOC(size) palloc(size) #define FREE(ptr) pfree(ptr) #else #define ALLOC(size) od_malloc(size) #define FREE(ptr) od_free(ptr) #endif /* Set of error states */ typedef enum pg_hmac_errno { PG_HMAC_ERROR_NONE = 0, PG_HMAC_ERROR_OOM, PG_HMAC_ERROR_INTERNAL, } pg_hmac_errno; /* Internal pg_hmac_ctx structure */ struct pg_hmac_ctx { pg_cryptohash_ctx *hash; pg_cryptohash_type type; pg_hmac_errno error; const char *errreason; int block_size; int digest_size; /* * Use the largest block size among supported options. This wastes some * memory but simplifies the allocation logic. */ uint8 k_ipad[PG_SHA512_BLOCK_LENGTH]; uint8 k_opad[PG_SHA512_BLOCK_LENGTH]; }; #define HMAC_IPAD 0x36 #define HMAC_OPAD 0x5C /* * pg_hmac_create * * Allocate a hash context. Returns NULL on failure for an OOM. The * backend issues an error, without returning. */ pg_hmac_ctx *pg_hmac_create(pg_cryptohash_type type) { pg_hmac_ctx *ctx; ctx = ALLOC(sizeof(pg_hmac_ctx)); if (ctx == NULL) { return NULL; } memset(ctx, 0, sizeof(pg_hmac_ctx)); ctx->type = type; ctx->error = PG_HMAC_ERROR_NONE; ctx->errreason = NULL; /* * Initialize the context data. This requires to know the digest and * block lengths, that depend on the type of hash used. */ switch (type) { case PG_MD5: ctx->digest_size = MD5_DIGEST_LENGTH; ctx->block_size = MD5_BLOCK_SIZE; break; case PG_SHA1: ctx->digest_size = SHA1_DIGEST_LENGTH; ctx->block_size = SHA1_BLOCK_SIZE; break; case PG_SHA224: ctx->digest_size = PG_SHA224_DIGEST_LENGTH; ctx->block_size = PG_SHA224_BLOCK_LENGTH; break; case PG_SHA256: ctx->digest_size = PG_SHA256_DIGEST_LENGTH; ctx->block_size = PG_SHA256_BLOCK_LENGTH; break; case PG_SHA384: ctx->digest_size = PG_SHA384_DIGEST_LENGTH; ctx->block_size = PG_SHA384_BLOCK_LENGTH; break; case PG_SHA512: ctx->digest_size = PG_SHA512_DIGEST_LENGTH; ctx->block_size = PG_SHA512_BLOCK_LENGTH; break; } ctx->hash = pg_cryptohash_create(type); if (ctx->hash == NULL) { explicit_bzero(ctx, sizeof(pg_hmac_ctx)); FREE(ctx); return NULL; } return ctx; } /* * pg_hmac_init * * Initialize a HMAC context. Returns 0 on success, -1 on failure. */ int pg_hmac_init(pg_hmac_ctx *ctx, const uint8 *key, size_t len) { int i; int digest_size; int block_size; uint8 *shrinkbuf = NULL; if (ctx == NULL) { return -1; } digest_size = ctx->digest_size; block_size = ctx->block_size; memset(ctx->k_opad, HMAC_OPAD, ctx->block_size); memset(ctx->k_ipad, HMAC_IPAD, ctx->block_size); /* * If the key is longer than the block size, pass it through the hash once * to shrink it down. */ if (len > block_size) { pg_cryptohash_ctx *hash_ctx; /* temporary buffer for one-time shrink */ shrinkbuf = ALLOC(digest_size); if (shrinkbuf == NULL) { ctx->error = PG_HMAC_ERROR_OOM; return -1; } memset(shrinkbuf, 0, digest_size); hash_ctx = pg_cryptohash_create(ctx->type); if (hash_ctx == NULL) { ctx->error = PG_HMAC_ERROR_OOM; FREE(shrinkbuf); return -1; } if (pg_cryptohash_init(hash_ctx) < 0 || pg_cryptohash_update(hash_ctx, key, len) < 0 || pg_cryptohash_final(hash_ctx, shrinkbuf, digest_size) < 0) { ctx->error = PG_HMAC_ERROR_INTERNAL; ctx->errreason = pg_cryptohash_error(hash_ctx); pg_cryptohash_free(hash_ctx); FREE(shrinkbuf); return -1; } key = shrinkbuf; len = digest_size; pg_cryptohash_free(hash_ctx); } for (i = 0; i < len; i++) { ctx->k_ipad[i] ^= key[i]; ctx->k_opad[i] ^= key[i]; } /* tmp = H(K XOR ipad, text) */ if (pg_cryptohash_init(ctx->hash) < 0 || pg_cryptohash_update(ctx->hash, ctx->k_ipad, ctx->block_size) < 0) { ctx->error = PG_HMAC_ERROR_INTERNAL; ctx->errreason = pg_cryptohash_error(ctx->hash); if (shrinkbuf) { FREE(shrinkbuf); } return -1; } if (shrinkbuf) { FREE(shrinkbuf); } return 0; } /* * pg_hmac_update * * Update a HMAC context. Returns 0 on success, -1 on failure. */ int pg_hmac_update(pg_hmac_ctx *ctx, const uint8 *data, size_t len) { if (ctx == NULL) { return -1; } if (pg_cryptohash_update(ctx->hash, data, len) < 0) { ctx->error = PG_HMAC_ERROR_INTERNAL; ctx->errreason = pg_cryptohash_error(ctx->hash); return -1; } return 0; } /* * pg_hmac_final * * Finalize a HMAC context. Returns 0 on success, -1 on failure. */ int pg_hmac_final(pg_hmac_ctx *ctx, uint8 *dest, size_t len) { uint8 *h; if (ctx == NULL) { return -1; } h = ALLOC(ctx->digest_size); if (h == NULL) { ctx->error = PG_HMAC_ERROR_OOM; return -1; } memset(h, 0, ctx->digest_size); if (pg_cryptohash_final(ctx->hash, h, ctx->digest_size) < 0) { ctx->error = PG_HMAC_ERROR_INTERNAL; ctx->errreason = pg_cryptohash_error(ctx->hash); FREE(h); return -1; } /* H(K XOR opad, tmp) */ if (pg_cryptohash_init(ctx->hash) < 0 || pg_cryptohash_update(ctx->hash, ctx->k_opad, ctx->block_size) < 0 || pg_cryptohash_update(ctx->hash, h, ctx->digest_size) < 0 || pg_cryptohash_final(ctx->hash, dest, len) < 0) { ctx->error = PG_HMAC_ERROR_INTERNAL; ctx->errreason = pg_cryptohash_error(ctx->hash); FREE(h); return -1; } FREE(h); return 0; } /* * pg_hmac_free * * Free a HMAC context. */ void pg_hmac_free(pg_hmac_ctx *ctx) { if (ctx == NULL) { return; } pg_cryptohash_free(ctx->hash); explicit_bzero(ctx, sizeof(pg_hmac_ctx)); FREE(ctx); } /* * pg_hmac_error * * Returns a static string providing details about an error that happened * during a HMAC computation. */ const char *pg_hmac_error(pg_hmac_ctx *ctx) { if (ctx == NULL) { return _("out of memory"); } /* * If a reason is provided, rely on it, else fallback to any error code * set. */ if (ctx->errreason) { return ctx->errreason; } switch (ctx->error) { case PG_HMAC_ERROR_NONE: return _("success"); case PG_HMAC_ERROR_INTERNAL: return _("internal error"); case PG_HMAC_ERROR_OOM: return _("out of memory"); } Assert(false); /* cannot be reached */ return _("success"); } odyssey-1.6.0-rc1/sources/common/md5.c000066400000000000000000000262101526064202200175040ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * md5.c * Implements the MD5 Message-Digest Algorithm * * Fallback implementation of MD5, as specified in RFC 1321. This * implementation is a simple one, in that it needs every input byte * to be buffered before doing any calculations. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/common/md5.c * *------------------------------------------------------------------------- */ /* $KAME: md5.c,v 1.3 2000/02/22 14:01:17 itojun Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef FRONTEND #include "postgres.h" #else #include "postgres_fe.h" #endif #include "common/md5_int.h" #define SHIFT(X, s) (((X) << (s)) | ((X) >> (32 - (s)))) #define F(X, Y, Z) (((X) & (Y)) | ((~X) & (Z))) #define G(X, Y, Z) (((X) & (Z)) | ((Y) & (~Z))) #define H(X, Y, Z) ((X) ^ (Y) ^ (Z)) #define I(X, Y, Z) ((Y) ^ ((X) | (~Z))) #define ROUND1(a, b, c, d, k, s, i) \ do { \ (a) = (a) + F((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } while (0) #define ROUND2(a, b, c, d, k, s, i) \ do { \ (a) = (a) + G((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } while (0) #define ROUND3(a, b, c, d, k, s, i) \ do { \ (a) = (a) + H((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } while (0) #define ROUND4(a, b, c, d, k, s, i) \ do { \ (a) = (a) + I((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } while (0) #define Sa 7 #define Sb 12 #define Sc 17 #define Sd 22 #define Se 5 #define Sf 9 #define Sg 14 #define Sh 20 #define Si 4 #define Sj 11 #define Sk 16 #define Sl 23 #define Sm 6 #define Sn 10 #define So 15 #define Sp 21 #define MD5_A0 0x67452301 #define MD5_B0 0xefcdab89 #define MD5_C0 0x98badcfe #define MD5_D0 0x10325476 /* Integer part of 4294967296 times abs(sin(i)), where i is in radians. */ static const uint32 T[65] = { 0, 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, }; static const uint8 md5_paddat[MD5_BUFLEN] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static void md5_calc(const uint8 *b64, pg_md5_ctx *ctx) { uint32 A = ctx->md5_sta; uint32 B = ctx->md5_stb; uint32 C = ctx->md5_stc; uint32 D = ctx->md5_std; #ifndef WORDS_BIGENDIAN const uint32 *X = (const uint32 *)b64; #else /* 4 byte words */ /* what a brute force but fast! */ uint32 X[16]; uint8 *y = (uint8 *)X; y[0] = b64[3]; y[1] = b64[2]; y[2] = b64[1]; y[3] = b64[0]; y[4] = b64[7]; y[5] = b64[6]; y[6] = b64[5]; y[7] = b64[4]; y[8] = b64[11]; y[9] = b64[10]; y[10] = b64[9]; y[11] = b64[8]; y[12] = b64[15]; y[13] = b64[14]; y[14] = b64[13]; y[15] = b64[12]; y[16] = b64[19]; y[17] = b64[18]; y[18] = b64[17]; y[19] = b64[16]; y[20] = b64[23]; y[21] = b64[22]; y[22] = b64[21]; y[23] = b64[20]; y[24] = b64[27]; y[25] = b64[26]; y[26] = b64[25]; y[27] = b64[24]; y[28] = b64[31]; y[29] = b64[30]; y[30] = b64[29]; y[31] = b64[28]; y[32] = b64[35]; y[33] = b64[34]; y[34] = b64[33]; y[35] = b64[32]; y[36] = b64[39]; y[37] = b64[38]; y[38] = b64[37]; y[39] = b64[36]; y[40] = b64[43]; y[41] = b64[42]; y[42] = b64[41]; y[43] = b64[40]; y[44] = b64[47]; y[45] = b64[46]; y[46] = b64[45]; y[47] = b64[44]; y[48] = b64[51]; y[49] = b64[50]; y[50] = b64[49]; y[51] = b64[48]; y[52] = b64[55]; y[53] = b64[54]; y[54] = b64[53]; y[55] = b64[52]; y[56] = b64[59]; y[57] = b64[58]; y[58] = b64[57]; y[59] = b64[56]; y[60] = b64[63]; y[61] = b64[62]; y[62] = b64[61]; y[63] = b64[60]; #endif ROUND1(A, B, C, D, 0, Sa, 1); ROUND1(D, A, B, C, 1, Sb, 2); ROUND1(C, D, A, B, 2, Sc, 3); ROUND1(B, C, D, A, 3, Sd, 4); ROUND1(A, B, C, D, 4, Sa, 5); ROUND1(D, A, B, C, 5, Sb, 6); ROUND1(C, D, A, B, 6, Sc, 7); ROUND1(B, C, D, A, 7, Sd, 8); ROUND1(A, B, C, D, 8, Sa, 9); ROUND1(D, A, B, C, 9, Sb, 10); ROUND1(C, D, A, B, 10, Sc, 11); ROUND1(B, C, D, A, 11, Sd, 12); ROUND1(A, B, C, D, 12, Sa, 13); ROUND1(D, A, B, C, 13, Sb, 14); ROUND1(C, D, A, B, 14, Sc, 15); ROUND1(B, C, D, A, 15, Sd, 16); ROUND2(A, B, C, D, 1, Se, 17); ROUND2(D, A, B, C, 6, Sf, 18); ROUND2(C, D, A, B, 11, Sg, 19); ROUND2(B, C, D, A, 0, Sh, 20); ROUND2(A, B, C, D, 5, Se, 21); ROUND2(D, A, B, C, 10, Sf, 22); ROUND2(C, D, A, B, 15, Sg, 23); ROUND2(B, C, D, A, 4, Sh, 24); ROUND2(A, B, C, D, 9, Se, 25); ROUND2(D, A, B, C, 14, Sf, 26); ROUND2(C, D, A, B, 3, Sg, 27); ROUND2(B, C, D, A, 8, Sh, 28); ROUND2(A, B, C, D, 13, Se, 29); ROUND2(D, A, B, C, 2, Sf, 30); ROUND2(C, D, A, B, 7, Sg, 31); ROUND2(B, C, D, A, 12, Sh, 32); ROUND3(A, B, C, D, 5, Si, 33); ROUND3(D, A, B, C, 8, Sj, 34); ROUND3(C, D, A, B, 11, Sk, 35); ROUND3(B, C, D, A, 14, Sl, 36); ROUND3(A, B, C, D, 1, Si, 37); ROUND3(D, A, B, C, 4, Sj, 38); ROUND3(C, D, A, B, 7, Sk, 39); ROUND3(B, C, D, A, 10, Sl, 40); ROUND3(A, B, C, D, 13, Si, 41); ROUND3(D, A, B, C, 0, Sj, 42); ROUND3(C, D, A, B, 3, Sk, 43); ROUND3(B, C, D, A, 6, Sl, 44); ROUND3(A, B, C, D, 9, Si, 45); ROUND3(D, A, B, C, 12, Sj, 46); ROUND3(C, D, A, B, 15, Sk, 47); ROUND3(B, C, D, A, 2, Sl, 48); ROUND4(A, B, C, D, 0, Sm, 49); ROUND4(D, A, B, C, 7, Sn, 50); ROUND4(C, D, A, B, 14, So, 51); ROUND4(B, C, D, A, 5, Sp, 52); ROUND4(A, B, C, D, 12, Sm, 53); ROUND4(D, A, B, C, 3, Sn, 54); ROUND4(C, D, A, B, 10, So, 55); ROUND4(B, C, D, A, 1, Sp, 56); ROUND4(A, B, C, D, 8, Sm, 57); ROUND4(D, A, B, C, 15, Sn, 58); ROUND4(C, D, A, B, 6, So, 59); ROUND4(B, C, D, A, 13, Sp, 60); ROUND4(A, B, C, D, 4, Sm, 61); ROUND4(D, A, B, C, 11, Sn, 62); ROUND4(C, D, A, B, 2, So, 63); ROUND4(B, C, D, A, 9, Sp, 64); ctx->md5_sta += A; ctx->md5_stb += B; ctx->md5_stc += C; ctx->md5_std += D; } static void md5_pad(pg_md5_ctx *ctx) { unsigned int gap; /* Don't count up padding. Keep md5_n. */ gap = MD5_BUFLEN - ctx->md5_i; if (gap > 8) { memmove(ctx->md5_buf + ctx->md5_i, md5_paddat, gap - sizeof(ctx->md5_n)); } else { /* including gap == 8 */ memmove(ctx->md5_buf + ctx->md5_i, md5_paddat, gap); md5_calc(ctx->md5_buf, ctx); memmove(ctx->md5_buf, md5_paddat + gap, MD5_BUFLEN - sizeof(ctx->md5_n)); } /* 8 byte word */ #ifndef WORDS_BIGENDIAN memmove(&ctx->md5_buf[56], &ctx->md5_n8[0], 8); #else ctx->md5_buf[56] = ctx->md5_n8[7]; ctx->md5_buf[57] = ctx->md5_n8[6]; ctx->md5_buf[58] = ctx->md5_n8[5]; ctx->md5_buf[59] = ctx->md5_n8[4]; ctx->md5_buf[60] = ctx->md5_n8[3]; ctx->md5_buf[61] = ctx->md5_n8[2]; ctx->md5_buf[62] = ctx->md5_n8[1]; ctx->md5_buf[63] = ctx->md5_n8[0]; #endif md5_calc(ctx->md5_buf, ctx); } static void md5_result(uint8 *digest, pg_md5_ctx *ctx) { /* 4 byte words */ #ifndef WORDS_BIGENDIAN memmove(digest, &ctx->md5_st8[0], 16); #else digest[0] = ctx->md5_st8[3]; digest[1] = ctx->md5_st8[2]; digest[2] = ctx->md5_st8[1]; digest[3] = ctx->md5_st8[0]; digest[4] = ctx->md5_st8[7]; digest[5] = ctx->md5_st8[6]; digest[6] = ctx->md5_st8[5]; digest[7] = ctx->md5_st8[4]; digest[8] = ctx->md5_st8[11]; digest[9] = ctx->md5_st8[10]; digest[10] = ctx->md5_st8[9]; digest[11] = ctx->md5_st8[8]; digest[12] = ctx->md5_st8[15]; digest[13] = ctx->md5_st8[14]; digest[14] = ctx->md5_st8[13]; digest[15] = ctx->md5_st8[12]; #endif } /* External routines for this MD5 implementation */ /* * pg_md5_init * * Initialize a MD5 context. */ void pg_md5_init(pg_md5_ctx *ctx) { ctx->md5_n = 0; ctx->md5_i = 0; ctx->md5_sta = MD5_A0; ctx->md5_stb = MD5_B0; ctx->md5_stc = MD5_C0; ctx->md5_std = MD5_D0; memset(ctx->md5_buf, 0, sizeof(ctx->md5_buf)); } /* * pg_md5_update * * Update a MD5 context. */ void pg_md5_update(pg_md5_ctx *ctx, const uint8 *data, size_t len) { unsigned int gap, i; ctx->md5_n += len * 8; /* byte to bit */ gap = MD5_BUFLEN - ctx->md5_i; if (len >= gap) { memmove(ctx->md5_buf + ctx->md5_i, data, gap); md5_calc(ctx->md5_buf, ctx); for (i = gap; i + MD5_BUFLEN <= len; i += MD5_BUFLEN) { md5_calc(data + i, ctx); } ctx->md5_i = len - i; memmove(ctx->md5_buf, data + i, ctx->md5_i); } else { memmove(ctx->md5_buf + ctx->md5_i, data, len); ctx->md5_i += len; } } /* * pg_md5_final * * Finalize a MD5 context. */ void pg_md5_final(pg_md5_ctx *ctx, uint8 *dest) { md5_pad(ctx); md5_result(dest, ctx); } odyssey-1.6.0-rc1/sources/common/saslprep.c000066400000000000000000000625551526064202200206640ustar00rootroot00000000000000/*------------------------------------------------------------------------- * saslprep.c * SASLprep normalization, for SCRAM authentication * * The SASLprep algorithm is used to process a user-supplied password into * canonical form. For more details, see: * * [RFC3454] Preparation of Internationalized Strings ("stringprep"), * http://www.ietf.org/rfc/rfc3454.txt * * [RFC4013] SASLprep: Stringprep Profile for User Names and Passwords * http://www.ietf.org/rfc/rfc4013.txt * * * Portions Copyright (c) 2017-2025, PostgreSQL Global Development Group * * IDENTIFICATION * src/common/saslprep.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #include "postgres.h" #include "utils/memutils.h" #else #include "postgres_fe.h" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wsign-compare" #endif #include "common/saslprep.h" #include "common/string.h" #include "common/unicode_norm.h" #include "mb/pg_wchar.h" /* * In backend, we will use palloc/pfree. In frontend, use malloc, and * return SASLPREP_OOM on out-of-memory. */ #ifndef FRONTEND #define STRDUP(s) pstrdup(s) #define ALLOC(size) palloc(size) #define FREE(size) pfree(size) #else #define STRDUP(s) od_strdup(s) #define ALLOC(size) od_malloc(size) #define FREE(size) od_free(size) #endif /* Prototypes for local functions */ static int codepoint_range_cmp(const void *a, const void *b); static bool is_code_in_table(char32_t code, const char32_t *map, int mapsize); static int pg_utf8_string_len(const char *source); /* * Stringprep Mapping Tables. * * The stringprep specification includes a number of tables of Unicode * codepoints, used in different parts of the algorithm. They are below, * as arrays of codepoint ranges. Each range is a pair of codepoints, * for the first and last codepoint included the range (inclusive!). */ /* * C.1.2 Non-ASCII space characters * * These are all mapped to the ASCII space character (U+00A0). */ static const char32_t non_ascii_space_ranges[] = { 0x00A0, 0x00A0, 0x1680, 0x1680, 0x2000, 0x200B, 0x202F, 0x202F, 0x205F, 0x205F, 0x3000, 0x3000 }; /* * B.1 Commonly mapped to nothing * * If any of these appear in the input, they are removed. */ static const char32_t commonly_mapped_to_nothing_ranges[] = { 0x00AD, 0x00AD, 0x034F, 0x034F, 0x1806, 0x1806, 0x180B, 0x180D, 0x200B, 0x200D, 0x2060, 0x2060, 0xFE00, 0xFE0F, 0xFEFF, 0xFEFF }; /* * prohibited_output_ranges is a union of all the characters from * the following tables: * * C.1.2 Non-ASCII space characters * C.2.1 ASCII control characters * C.2.2 Non-ASCII control characters * C.3 Private Use characters * C.4 Non-character code points * C.5 Surrogate code points * C.6 Inappropriate for plain text characters * C.7 Inappropriate for canonical representation characters * C.7 Change display properties or deprecated characters * C.8 Tagging characters * * These are the tables that are listed as "prohibited output" * characters in the SASLprep profile. * * The comment after each code range indicates which source table * the code came from. Note that there is some overlap in the source * tables, so one code might originate from multiple source tables. * Adjacent ranges have also been merged together, to save space. */ static const char32_t prohibited_output_ranges[] = { 0x0000, 0x001F, /* C.2.1 */ 0x007F, 0x00A0, /* C.1.2, C.2.1, C.2.2 */ 0x0340, 0x0341, /* C.8 */ 0x06DD, 0x06DD, /* C.2.2 */ 0x070F, 0x070F, /* C.2.2 */ 0x1680, 0x1680, /* C.1.2 */ 0x180E, 0x180E, /* C.2.2 */ 0x2000, 0x200F, /* C.1.2, C.2.2, C.8 */ 0x2028, 0x202F, /* C.1.2, C.2.2, C.8 */ 0x205F, 0x2063, /* C.1.2, C.2.2 */ 0x206A, 0x206F, /* C.2.2, C.8 */ 0x2FF0, 0x2FFB, /* C.7 */ 0x3000, 0x3000, /* C.1.2 */ 0xD800, 0xF8FF, /* C.3, C.5 */ 0xFDD0, 0xFDEF, /* C.4 */ 0xFEFF, 0xFEFF, /* C.2.2 */ 0xFFF9, 0xFFFF, /* C.2.2, C.4, C.6 */ 0x1D173, 0x1D17A, /* C.2.2 */ 0x1FFFE, 0x1FFFF, /* C.4 */ 0x2FFFE, 0x2FFFF, /* C.4 */ 0x3FFFE, 0x3FFFF, /* C.4 */ 0x4FFFE, 0x4FFFF, /* C.4 */ 0x5FFFE, 0x5FFFF, /* C.4 */ 0x6FFFE, 0x6FFFF, /* C.4 */ 0x7FFFE, 0x7FFFF, /* C.4 */ 0x8FFFE, 0x8FFFF, /* C.4 */ 0x9FFFE, 0x9FFFF, /* C.4 */ 0xAFFFE, 0xAFFFF, /* C.4 */ 0xBFFFE, 0xBFFFF, /* C.4 */ 0xCFFFE, 0xCFFFF, /* C.4 */ 0xDFFFE, 0xDFFFF, /* C.4 */ 0xE0001, 0xE0001, /* C.9 */ 0xE0020, 0xE007F, /* C.9 */ 0xEFFFE, 0xEFFFF, /* C.4 */ 0xF0000, 0xFFFFF, /* C.3, C.4 */ 0x100000, 0x10FFFF /* C.3, C.4 */ }; /* A.1 Unassigned code points in Unicode 3.2 */ static const char32_t unassigned_codepoint_ranges[] = { 0x0221, 0x0221, 0x0234, 0x024F, 0x02AE, 0x02AF, 0x02EF, 0x02FF, 0x0350, 0x035F, 0x0370, 0x0373, 0x0376, 0x0379, 0x037B, 0x037D, 0x037F, 0x0383, 0x038B, 0x038B, 0x038D, 0x038D, 0x03A2, 0x03A2, 0x03CF, 0x03CF, 0x03F7, 0x03FF, 0x0487, 0x0487, 0x04CF, 0x04CF, 0x04F6, 0x04F7, 0x04FA, 0x04FF, 0x0510, 0x0530, 0x0557, 0x0558, 0x0560, 0x0560, 0x0588, 0x0588, 0x058B, 0x0590, 0x05A2, 0x05A2, 0x05BA, 0x05BA, 0x05C5, 0x05CF, 0x05EB, 0x05EF, 0x05F5, 0x060B, 0x060D, 0x061A, 0x061C, 0x061E, 0x0620, 0x0620, 0x063B, 0x063F, 0x0656, 0x065F, 0x06EE, 0x06EF, 0x06FF, 0x06FF, 0x070E, 0x070E, 0x072D, 0x072F, 0x074B, 0x077F, 0x07B2, 0x0900, 0x0904, 0x0904, 0x093A, 0x093B, 0x094E, 0x094F, 0x0955, 0x0957, 0x0971, 0x0980, 0x0984, 0x0984, 0x098D, 0x098E, 0x0991, 0x0992, 0x09A9, 0x09A9, 0x09B1, 0x09B1, 0x09B3, 0x09B5, 0x09BA, 0x09BB, 0x09BD, 0x09BD, 0x09C5, 0x09C6, 0x09C9, 0x09CA, 0x09CE, 0x09D6, 0x09D8, 0x09DB, 0x09DE, 0x09DE, 0x09E4, 0x09E5, 0x09FB, 0x0A01, 0x0A03, 0x0A04, 0x0A0B, 0x0A0E, 0x0A11, 0x0A12, 0x0A29, 0x0A29, 0x0A31, 0x0A31, 0x0A34, 0x0A34, 0x0A37, 0x0A37, 0x0A3A, 0x0A3B, 0x0A3D, 0x0A3D, 0x0A43, 0x0A46, 0x0A49, 0x0A4A, 0x0A4E, 0x0A58, 0x0A5D, 0x0A5D, 0x0A5F, 0x0A65, 0x0A75, 0x0A80, 0x0A84, 0x0A84, 0x0A8C, 0x0A8C, 0x0A8E, 0x0A8E, 0x0A92, 0x0A92, 0x0AA9, 0x0AA9, 0x0AB1, 0x0AB1, 0x0AB4, 0x0AB4, 0x0ABA, 0x0ABB, 0x0AC6, 0x0AC6, 0x0ACA, 0x0ACA, 0x0ACE, 0x0ACF, 0x0AD1, 0x0ADF, 0x0AE1, 0x0AE5, 0x0AF0, 0x0B00, 0x0B04, 0x0B04, 0x0B0D, 0x0B0E, 0x0B11, 0x0B12, 0x0B29, 0x0B29, 0x0B31, 0x0B31, 0x0B34, 0x0B35, 0x0B3A, 0x0B3B, 0x0B44, 0x0B46, 0x0B49, 0x0B4A, 0x0B4E, 0x0B55, 0x0B58, 0x0B5B, 0x0B5E, 0x0B5E, 0x0B62, 0x0B65, 0x0B71, 0x0B81, 0x0B84, 0x0B84, 0x0B8B, 0x0B8D, 0x0B91, 0x0B91, 0x0B96, 0x0B98, 0x0B9B, 0x0B9B, 0x0B9D, 0x0B9D, 0x0BA0, 0x0BA2, 0x0BA5, 0x0BA7, 0x0BAB, 0x0BAD, 0x0BB6, 0x0BB6, 0x0BBA, 0x0BBD, 0x0BC3, 0x0BC5, 0x0BC9, 0x0BC9, 0x0BCE, 0x0BD6, 0x0BD8, 0x0BE6, 0x0BF3, 0x0C00, 0x0C04, 0x0C04, 0x0C0D, 0x0C0D, 0x0C11, 0x0C11, 0x0C29, 0x0C29, 0x0C34, 0x0C34, 0x0C3A, 0x0C3D, 0x0C45, 0x0C45, 0x0C49, 0x0C49, 0x0C4E, 0x0C54, 0x0C57, 0x0C5F, 0x0C62, 0x0C65, 0x0C70, 0x0C81, 0x0C84, 0x0C84, 0x0C8D, 0x0C8D, 0x0C91, 0x0C91, 0x0CA9, 0x0CA9, 0x0CB4, 0x0CB4, 0x0CBA, 0x0CBD, 0x0CC5, 0x0CC5, 0x0CC9, 0x0CC9, 0x0CCE, 0x0CD4, 0x0CD7, 0x0CDD, 0x0CDF, 0x0CDF, 0x0CE2, 0x0CE5, 0x0CF0, 0x0D01, 0x0D04, 0x0D04, 0x0D0D, 0x0D0D, 0x0D11, 0x0D11, 0x0D29, 0x0D29, 0x0D3A, 0x0D3D, 0x0D44, 0x0D45, 0x0D49, 0x0D49, 0x0D4E, 0x0D56, 0x0D58, 0x0D5F, 0x0D62, 0x0D65, 0x0D70, 0x0D81, 0x0D84, 0x0D84, 0x0D97, 0x0D99, 0x0DB2, 0x0DB2, 0x0DBC, 0x0DBC, 0x0DBE, 0x0DBF, 0x0DC7, 0x0DC9, 0x0DCB, 0x0DCE, 0x0DD5, 0x0DD5, 0x0DD7, 0x0DD7, 0x0DE0, 0x0DF1, 0x0DF5, 0x0E00, 0x0E3B, 0x0E3E, 0x0E5C, 0x0E80, 0x0E83, 0x0E83, 0x0E85, 0x0E86, 0x0E89, 0x0E89, 0x0E8B, 0x0E8C, 0x0E8E, 0x0E93, 0x0E98, 0x0E98, 0x0EA0, 0x0EA0, 0x0EA4, 0x0EA4, 0x0EA6, 0x0EA6, 0x0EA8, 0x0EA9, 0x0EAC, 0x0EAC, 0x0EBA, 0x0EBA, 0x0EBE, 0x0EBF, 0x0EC5, 0x0EC5, 0x0EC7, 0x0EC7, 0x0ECE, 0x0ECF, 0x0EDA, 0x0EDB, 0x0EDE, 0x0EFF, 0x0F48, 0x0F48, 0x0F6B, 0x0F70, 0x0F8C, 0x0F8F, 0x0F98, 0x0F98, 0x0FBD, 0x0FBD, 0x0FCD, 0x0FCE, 0x0FD0, 0x0FFF, 0x1022, 0x1022, 0x1028, 0x1028, 0x102B, 0x102B, 0x1033, 0x1035, 0x103A, 0x103F, 0x105A, 0x109F, 0x10C6, 0x10CF, 0x10F9, 0x10FA, 0x10FC, 0x10FF, 0x115A, 0x115E, 0x11A3, 0x11A7, 0x11FA, 0x11FF, 0x1207, 0x1207, 0x1247, 0x1247, 0x1249, 0x1249, 0x124E, 0x124F, 0x1257, 0x1257, 0x1259, 0x1259, 0x125E, 0x125F, 0x1287, 0x1287, 0x1289, 0x1289, 0x128E, 0x128F, 0x12AF, 0x12AF, 0x12B1, 0x12B1, 0x12B6, 0x12B7, 0x12BF, 0x12BF, 0x12C1, 0x12C1, 0x12C6, 0x12C7, 0x12CF, 0x12CF, 0x12D7, 0x12D7, 0x12EF, 0x12EF, 0x130F, 0x130F, 0x1311, 0x1311, 0x1316, 0x1317, 0x131F, 0x131F, 0x1347, 0x1347, 0x135B, 0x1360, 0x137D, 0x139F, 0x13F5, 0x1400, 0x1677, 0x167F, 0x169D, 0x169F, 0x16F1, 0x16FF, 0x170D, 0x170D, 0x1715, 0x171F, 0x1737, 0x173F, 0x1754, 0x175F, 0x176D, 0x176D, 0x1771, 0x1771, 0x1774, 0x177F, 0x17DD, 0x17DF, 0x17EA, 0x17FF, 0x180F, 0x180F, 0x181A, 0x181F, 0x1878, 0x187F, 0x18AA, 0x1DFF, 0x1E9C, 0x1E9F, 0x1EFA, 0x1EFF, 0x1F16, 0x1F17, 0x1F1E, 0x1F1F, 0x1F46, 0x1F47, 0x1F4E, 0x1F4F, 0x1F58, 0x1F58, 0x1F5A, 0x1F5A, 0x1F5C, 0x1F5C, 0x1F5E, 0x1F5E, 0x1F7E, 0x1F7F, 0x1FB5, 0x1FB5, 0x1FC5, 0x1FC5, 0x1FD4, 0x1FD5, 0x1FDC, 0x1FDC, 0x1FF0, 0x1FF1, 0x1FF5, 0x1FF5, 0x1FFF, 0x1FFF, 0x2053, 0x2056, 0x2058, 0x205E, 0x2064, 0x2069, 0x2072, 0x2073, 0x208F, 0x209F, 0x20B2, 0x20CF, 0x20EB, 0x20FF, 0x213B, 0x213C, 0x214C, 0x2152, 0x2184, 0x218F, 0x23CF, 0x23FF, 0x2427, 0x243F, 0x244B, 0x245F, 0x24FF, 0x24FF, 0x2614, 0x2615, 0x2618, 0x2618, 0x267E, 0x267F, 0x268A, 0x2700, 0x2705, 0x2705, 0x270A, 0x270B, 0x2728, 0x2728, 0x274C, 0x274C, 0x274E, 0x274E, 0x2753, 0x2755, 0x2757, 0x2757, 0x275F, 0x2760, 0x2795, 0x2797, 0x27B0, 0x27B0, 0x27BF, 0x27CF, 0x27EC, 0x27EF, 0x2B00, 0x2E7F, 0x2E9A, 0x2E9A, 0x2EF4, 0x2EFF, 0x2FD6, 0x2FEF, 0x2FFC, 0x2FFF, 0x3040, 0x3040, 0x3097, 0x3098, 0x3100, 0x3104, 0x312D, 0x3130, 0x318F, 0x318F, 0x31B8, 0x31EF, 0x321D, 0x321F, 0x3244, 0x3250, 0x327C, 0x327E, 0x32CC, 0x32CF, 0x32FF, 0x32FF, 0x3377, 0x337A, 0x33DE, 0x33DF, 0x33FF, 0x33FF, 0x4DB6, 0x4DFF, 0x9FA6, 0x9FFF, 0xA48D, 0xA48F, 0xA4C7, 0xABFF, 0xD7A4, 0xD7FF, 0xFA2E, 0xFA2F, 0xFA6B, 0xFAFF, 0xFB07, 0xFB12, 0xFB18, 0xFB1C, 0xFB37, 0xFB37, 0xFB3D, 0xFB3D, 0xFB3F, 0xFB3F, 0xFB42, 0xFB42, 0xFB45, 0xFB45, 0xFBB2, 0xFBD2, 0xFD40, 0xFD4F, 0xFD90, 0xFD91, 0xFDC8, 0xFDCF, 0xFDFD, 0xFDFF, 0xFE10, 0xFE1F, 0xFE24, 0xFE2F, 0xFE47, 0xFE48, 0xFE53, 0xFE53, 0xFE67, 0xFE67, 0xFE6C, 0xFE6F, 0xFE75, 0xFE75, 0xFEFD, 0xFEFE, 0xFF00, 0xFF00, 0xFFBF, 0xFFC1, 0xFFC8, 0xFFC9, 0xFFD0, 0xFFD1, 0xFFD8, 0xFFD9, 0xFFDD, 0xFFDF, 0xFFE7, 0xFFE7, 0xFFEF, 0xFFF8, 0x10000, 0x102FF, 0x1031F, 0x1031F, 0x10324, 0x1032F, 0x1034B, 0x103FF, 0x10426, 0x10427, 0x1044E, 0x1CFFF, 0x1D0F6, 0x1D0FF, 0x1D127, 0x1D129, 0x1D1DE, 0x1D3FF, 0x1D455, 0x1D455, 0x1D49D, 0x1D49D, 0x1D4A0, 0x1D4A1, 0x1D4A3, 0x1D4A4, 0x1D4A7, 0x1D4A8, 0x1D4AD, 0x1D4AD, 0x1D4BA, 0x1D4BA, 0x1D4BC, 0x1D4BC, 0x1D4C1, 0x1D4C1, 0x1D4C4, 0x1D4C4, 0x1D506, 0x1D506, 0x1D50B, 0x1D50C, 0x1D515, 0x1D515, 0x1D51D, 0x1D51D, 0x1D53A, 0x1D53A, 0x1D53F, 0x1D53F, 0x1D545, 0x1D545, 0x1D547, 0x1D549, 0x1D551, 0x1D551, 0x1D6A4, 0x1D6A7, 0x1D7CA, 0x1D7CD, 0x1D800, 0x1FFFD, 0x2A6D7, 0x2F7FF, 0x2FA1E, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, 0xD0000, 0xDFFFD, 0xE0000, 0xE0000, 0xE0002, 0xE001F, 0xE0080, 0xEFFFD }; /* D.1 Characters with bidirectional property "R" or "AL" */ static const char32_t RandALCat_codepoint_ranges[] = { 0x05BE, 0x05BE, 0x05C0, 0x05C0, 0x05C3, 0x05C3, 0x05D0, 0x05EA, 0x05F0, 0x05F4, 0x061B, 0x061B, 0x061F, 0x061F, 0x0621, 0x063A, 0x0640, 0x064A, 0x066D, 0x066F, 0x0671, 0x06D5, 0x06DD, 0x06DD, 0x06E5, 0x06E6, 0x06FA, 0x06FE, 0x0700, 0x070D, 0x0710, 0x0710, 0x0712, 0x072C, 0x0780, 0x07A5, 0x07B1, 0x07B1, 0x200F, 0x200F, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, 0xFB2A, 0xFB36, 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, 0xFDF0, 0xFDFC, 0xFE70, 0xFE74, 0xFE76, 0xFEFC }; /* D.2 Characters with bidirectional property "L" */ static const char32_t LCat_codepoint_ranges[] = { 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B5, 0x00B5, 0x00BA, 0x00BA, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x0220, 0x0222, 0x0233, 0x0250, 0x02AD, 0x02B0, 0x02B8, 0x02BB, 0x02C1, 0x02D0, 0x02D1, 0x02E0, 0x02E4, 0x02EE, 0x02EE, 0x037A, 0x037A, 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x03A1, 0x03A3, 0x03CE, 0x03D0, 0x03F5, 0x0400, 0x0482, 0x048A, 0x04CE, 0x04D0, 0x04F5, 0x04F8, 0x04F9, 0x0500, 0x050F, 0x0531, 0x0556, 0x0559, 0x055F, 0x0561, 0x0587, 0x0589, 0x0589, 0x0903, 0x0903, 0x0905, 0x0939, 0x093D, 0x0940, 0x0949, 0x094C, 0x0950, 0x0950, 0x0958, 0x0961, 0x0964, 0x0970, 0x0982, 0x0983, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BE, 0x09C0, 0x09C7, 0x09C8, 0x09CB, 0x09CC, 0x09D7, 0x09D7, 0x09DC, 0x09DD, 0x09DF, 0x09E1, 0x09E6, 0x09F1, 0x09F4, 0x09FA, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, 0x0A3E, 0x0A40, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, 0x0A66, 0x0A6F, 0x0A72, 0x0A74, 0x0A83, 0x0A83, 0x0A85, 0x0A8B, 0x0A8D, 0x0A8D, 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABD, 0x0AC0, 0x0AC9, 0x0AC9, 0x0ACB, 0x0ACC, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE0, 0x0AE6, 0x0AEF, 0x0B02, 0x0B03, 0x0B05, 0x0B0C, 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, 0x0B36, 0x0B39, 0x0B3D, 0x0B3E, 0x0B40, 0x0B40, 0x0B47, 0x0B48, 0x0B4B, 0x0B4C, 0x0B57, 0x0B57, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, 0x0B66, 0x0B70, 0x0B83, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB5, 0x0BB7, 0x0BB9, 0x0BBE, 0x0BBF, 0x0BC1, 0x0BC2, 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCC, 0x0BD7, 0x0BD7, 0x0BE7, 0x0BF2, 0x0C01, 0x0C03, 0x0C05, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, 0x0C2A, 0x0C33, 0x0C35, 0x0C39, 0x0C41, 0x0C44, 0x0C60, 0x0C61, 0x0C66, 0x0C6F, 0x0C82, 0x0C83, 0x0C85, 0x0C8C, 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, 0x0CBE, 0x0CBE, 0x0CC0, 0x0CC4, 0x0CC7, 0x0CC8, 0x0CCA, 0x0CCB, 0x0CD5, 0x0CD6, 0x0CDE, 0x0CDE, 0x0CE0, 0x0CE1, 0x0CE6, 0x0CEF, 0x0D02, 0x0D03, 0x0D05, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, 0x0D28, 0x0D2A, 0x0D39, 0x0D3E, 0x0D40, 0x0D46, 0x0D48, 0x0D4A, 0x0D4C, 0x0D57, 0x0D57, 0x0D60, 0x0D61, 0x0D66, 0x0D6F, 0x0D82, 0x0D83, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0DCF, 0x0DD1, 0x0DD8, 0x0DDF, 0x0DF2, 0x0DF4, 0x0E01, 0x0E30, 0x0E32, 0x0E33, 0x0E40, 0x0E46, 0x0E4F, 0x0E5B, 0x0E81, 0x0E82, 0x0E84, 0x0E84, 0x0E87, 0x0E88, 0x0E8A, 0x0E8A, 0x0E8D, 0x0E8D, 0x0E94, 0x0E97, 0x0E99, 0x0E9F, 0x0EA1, 0x0EA3, 0x0EA5, 0x0EA5, 0x0EA7, 0x0EA7, 0x0EAA, 0x0EAB, 0x0EAD, 0x0EB0, 0x0EB2, 0x0EB3, 0x0EBD, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0ED0, 0x0ED9, 0x0EDC, 0x0EDD, 0x0F00, 0x0F17, 0x0F1A, 0x0F34, 0x0F36, 0x0F36, 0x0F38, 0x0F38, 0x0F3E, 0x0F47, 0x0F49, 0x0F6A, 0x0F7F, 0x0F7F, 0x0F85, 0x0F85, 0x0F88, 0x0F8B, 0x0FBE, 0x0FC5, 0x0FC7, 0x0FCC, 0x0FCF, 0x0FCF, 0x1000, 0x1021, 0x1023, 0x1027, 0x1029, 0x102A, 0x102C, 0x102C, 0x1031, 0x1031, 0x1038, 0x1038, 0x1040, 0x1057, 0x10A0, 0x10C5, 0x10D0, 0x10F8, 0x10FB, 0x10FB, 0x1100, 0x1159, 0x115F, 0x11A2, 0x11A8, 0x11F9, 0x1200, 0x1206, 0x1208, 0x1246, 0x1248, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1286, 0x1288, 0x1288, 0x128A, 0x128D, 0x1290, 0x12AE, 0x12B0, 0x12B0, 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, 0x12C8, 0x12CE, 0x12D0, 0x12D6, 0x12D8, 0x12EE, 0x12F0, 0x130E, 0x1310, 0x1310, 0x1312, 0x1315, 0x1318, 0x131E, 0x1320, 0x1346, 0x1348, 0x135A, 0x1361, 0x137C, 0x13A0, 0x13F4, 0x1401, 0x1676, 0x1681, 0x169A, 0x16A0, 0x16F0, 0x1700, 0x170C, 0x170E, 0x1711, 0x1720, 0x1731, 0x1735, 0x1736, 0x1740, 0x1751, 0x1760, 0x176C, 0x176E, 0x1770, 0x1780, 0x17B6, 0x17BE, 0x17C5, 0x17C7, 0x17C8, 0x17D4, 0x17DA, 0x17DC, 0x17DC, 0x17E0, 0x17E9, 0x1810, 0x1819, 0x1820, 0x1877, 0x1880, 0x18A8, 0x1E00, 0x1E9B, 0x1EA0, 0x1EF9, 0x1F00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x200E, 0x200E, 0x2071, 0x2071, 0x207F, 0x207F, 0x2102, 0x2102, 0x2107, 0x2107, 0x210A, 0x2113, 0x2115, 0x2115, 0x2119, 0x211D, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x212D, 0x212F, 0x2131, 0x2133, 0x2139, 0x213D, 0x213F, 0x2145, 0x2149, 0x2160, 0x2183, 0x2336, 0x237A, 0x2395, 0x2395, 0x249C, 0x24E9, 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, 0x3041, 0x3096, 0x309D, 0x309F, 0x30A1, 0x30FA, 0x30FC, 0x30FF, 0x3105, 0x312C, 0x3131, 0x318E, 0x3190, 0x31B7, 0x31F0, 0x321C, 0x3220, 0x3243, 0x3260, 0x327B, 0x327F, 0x32B0, 0x32C0, 0x32CB, 0x32D0, 0x32FE, 0x3300, 0x3376, 0x337B, 0x33DD, 0x33E0, 0x33FE, 0x3400, 0x4DB5, 0x4E00, 0x9FA5, 0xA000, 0xA48C, 0xAC00, 0xD7A3, 0xD800, 0xFA2D, 0xFA30, 0xFA6A, 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, 0xFF66, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10300, 0x1031E, 0x10320, 0x10323, 0x10330, 0x1034A, 0x10400, 0x10425, 0x10428, 0x1044D, 0x1D000, 0x1D0F5, 0x1D100, 0x1D126, 0x1D12A, 0x1D166, 0x1D16A, 0x1D172, 0x1D183, 0x1D184, 0x1D18C, 0x1D1A9, 0x1D1AE, 0x1D1DD, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C0, 0x1D4C2, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A3, 0x1D6A8, 0x1D7C9, 0x20000, 0x2A6D6, 0x2F800, 0x2FA1D, 0xF0000, 0xFFFFD, 0x100000, 0x10FFFD }; /* End of stringprep tables */ /* Is the given Unicode codepoint in the given table of ranges? */ #define IS_CODE_IN_TABLE(code, map) is_code_in_table(code, map, lengthof(map)) static int codepoint_range_cmp(const void *a, const void *b) { const char32_t *key = (const char32_t *)a; const char32_t *range = (const char32_t *)b; if (*key < range[0]) { return -1; /* less than lower bound */ } if (*key > range[1]) { return 1; /* greater than upper bound */ } return 0; /* within range */ } static bool is_code_in_table(char32_t code, const char32_t *map, int mapsize) { Assert(mapsize % 2 == 0); if (code < map[0] || code > map[mapsize - 1]) { return false; } if (bsearch(&code, map, mapsize / 2, sizeof(char32_t) * 2, codepoint_range_cmp)) { return true; } else { return false; } } /* * Calculate the length in characters of a null-terminated UTF-8 string. * * Returns -1 if the input is not valid UTF-8. */ static int pg_utf8_string_len(const char *source) { const unsigned char *p = (const unsigned char *)source; int l; int num_chars = 0; size_t len = strlen(source); while (len) { l = pg_utf_mblen(p); if (len < l || !pg_utf8_islegal(p, l)) { return -1; } p += l; len -= l; num_chars++; } return num_chars; } /* * pg_saslprep - Normalize a password with SASLprep. * * SASLprep requires the input to be in UTF-8 encoding, but PostgreSQL * supports many encodings, so we don't blindly assume that. pg_saslprep * will check if the input looks like valid UTF-8, and returns * SASLPREP_INVALID_UTF8 if not. * * If the string contains prohibited characters (or more precisely, if the * output string would contain prohibited characters after normalization), * returns SASLPREP_PROHIBITED. * * On success, returns SASLPREP_SUCCESS, and the normalized string in * *output. * * In frontend, the normalized string is malloc'd, and the caller is * responsible for freeing it. If an allocation fails, returns * SASLPREP_OOM. In backend, the normalized string is palloc'd instead, * and a failed allocation leads to ereport(ERROR). */ pg_saslprep_rc pg_saslprep(const char *input, char **output) { char32_t *input_chars = NULL; char32_t *output_chars = NULL; int input_size; char *result; int result_size; int count; int i; bool contains_RandALCat; unsigned char *p; char32_t *wp; /* Ensure we return *output as NULL on failure */ *output = NULL; /* * Quick check if the input is pure ASCII. An ASCII string requires no * further processing. */ if (pg_is_ascii(input)) { *output = STRDUP(input); if (!(*output)) { goto oom; } return SASLPREP_SUCCESS; } /* * Convert the input from UTF-8 to an array of Unicode codepoints. * * This also checks that the input is a legal UTF-8 string. */ input_size = pg_utf8_string_len(input); if (input_size < 0) { return SASLPREP_INVALID_UTF8; } if (input_size >= MaxAllocSize / sizeof(char32_t)) { goto oom; } input_chars = ALLOC((input_size + 1) * sizeof(char32_t)); if (!input_chars) { goto oom; } p = (unsigned char *)input; for (i = 0; i < input_size; i++) { input_chars[i] = utf8_to_unicode(p); p += pg_utf_mblen(p); } input_chars[i] = (char32_t)'\0'; /* * The steps below correspond to the steps listed in [RFC3454], Section * "2. Preparation Overview" */ /* * 1) Map -- For each character in the input, check if it has a mapping * and, if so, replace it with its mapping. */ count = 0; for (i = 0; i < input_size; i++) { char32_t code = input_chars[i]; if (IS_CODE_IN_TABLE(code, non_ascii_space_ranges)) { input_chars[count++] = 0x0020; } else if (IS_CODE_IN_TABLE( code, commonly_mapped_to_nothing_ranges)) { /* map to nothing */ } else { input_chars[count++] = code; } } input_chars[count] = (char32_t)'\0'; input_size = count; if (input_size == 0) { goto prohibited; /* don't allow empty password */ } /* * 2) Normalize -- Normalize the result of step 1 using Unicode * normalization. */ output_chars = unicode_normalize(UNICODE_NFKC, input_chars); if (!output_chars) { goto oom; } /* * 3) Prohibit -- Check for any characters that are not allowed in the * output. If any are found, return an error. */ for (i = 0; i < input_size; i++) { char32_t code = input_chars[i]; if (IS_CODE_IN_TABLE(code, prohibited_output_ranges)) { goto prohibited; } if (IS_CODE_IN_TABLE(code, unassigned_codepoint_ranges)) { goto prohibited; } } /* * 4) Check bidi -- Possibly check for right-to-left characters, and if * any are found, make sure that the whole string satisfies the * requirements for bidirectional strings. If the string does not satisfy * the requirements for bidirectional strings, return an error. * * [RFC3454], Section "6. Bidirectional Characters" explains in more * detail what that means: * * "In any profile that specifies bidirectional character handling, all * three of the following requirements MUST be met: * * 1) The characters in section 5.8 MUST be prohibited. * * 2) If a string contains any RandALCat character, the string MUST NOT * contain any LCat character. * * 3) If a string contains any RandALCat character, a RandALCat character * MUST be the first character of the string, and a RandALCat character * MUST be the last character of the string." */ contains_RandALCat = false; for (i = 0; i < input_size; i++) { char32_t code = input_chars[i]; if (IS_CODE_IN_TABLE(code, RandALCat_codepoint_ranges)) { contains_RandALCat = true; break; } } if (contains_RandALCat) { char32_t first = input_chars[0]; char32_t last = input_chars[input_size - 1]; for (i = 0; i < input_size; i++) { char32_t code = input_chars[i]; if (IS_CODE_IN_TABLE(code, LCat_codepoint_ranges)) { goto prohibited; } } if (!IS_CODE_IN_TABLE(first, RandALCat_codepoint_ranges) || !IS_CODE_IN_TABLE(last, RandALCat_codepoint_ranges)) { goto prohibited; } } /* * Finally, convert the result back to UTF-8. */ result_size = 0; for (wp = output_chars; *wp; wp++) { unsigned char buf[4]; unicode_to_utf8(*wp, buf); result_size += pg_utf_mblen(buf); } result = ALLOC(result_size + 1); if (!result) { goto oom; } /* * There are no error exits below here, so the error exit paths don't need * to worry about possibly freeing "result". */ p = (unsigned char *)result; for (wp = output_chars; *wp; wp++) { unicode_to_utf8(*wp, p); p += pg_utf_mblen(p); } Assert((char *)p == result + result_size); *p = '\0'; FREE(input_chars); FREE(output_chars); *output = result; return SASLPREP_SUCCESS; prohibited: if (input_chars) { FREE(input_chars); } if (output_chars) { FREE(output_chars); } return SASLPREP_PROHIBITED; oom: if (input_chars) { FREE(input_chars); } if (output_chars) { FREE(output_chars); } return SASLPREP_OOM; } odyssey-1.6.0-rc1/sources/common/scram-common.c000066400000000000000000000175011526064202200214150ustar00rootroot00000000000000/*------------------------------------------------------------------------- * scram-common.c * Shared frontend/backend code for SCRAM authentication * * This contains the common low-level functions needed in both frontend and * backend, for implement the Salted Challenge Response Authentication * Mechanism (SCRAM), per IETF's RFC 5802. * * Portions Copyright (c) 2017-2025, PostgreSQL Global Development Group * * IDENTIFICATION * src/common/scram-common.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #include "postgres.h" #else #include "postgres_fe.h" #endif #include "common/base64.h" #include "common/hmac.h" #include "common/scram-common.h" #ifndef FRONTEND #include "miscadmin.h" #endif #include "port/pg_bswap.h" /* * Calculate SaltedPassword. * * The password should already be normalized by SASLprep. Returns 0 on * success, -1 on failure with *errstr pointing to a message about the * error details. */ int scram_SaltedPassword(const char *password, pg_cryptohash_type hash_type, int key_length, const uint8 *salt, int saltlen, int iterations, uint8 *result, const char **errstr) { int password_len = strlen(password); uint32 one = pg_hton32(1); int i, j; uint8 Ui[SCRAM_MAX_KEY_LEN]; uint8 Ui_prev[SCRAM_MAX_KEY_LEN]; pg_hmac_ctx *hmac_ctx = pg_hmac_create(hash_type); if (hmac_ctx == NULL) { *errstr = pg_hmac_error(NULL); /* returns OOM */ return -1; } /* * Iterate hash calculation of HMAC entry using given salt. This is * essentially PBKDF2 (see RFC2898) with HMAC() as the pseudorandom * function. */ /* First iteration */ if (pg_hmac_init(hmac_ctx, (uint8 *)password, password_len) < 0 || pg_hmac_update(hmac_ctx, salt, saltlen) < 0 || pg_hmac_update(hmac_ctx, (uint8 *)&one, sizeof(uint32)) < 0 || pg_hmac_final(hmac_ctx, Ui_prev, key_length) < 0) { *errstr = pg_hmac_error(hmac_ctx); pg_hmac_free(hmac_ctx); return -1; } memcpy(result, Ui_prev, key_length); /* Subsequent iterations */ for (i = 1; i < iterations; i++) { #ifndef FRONTEND /* * Make sure that this is interruptible as scram_iterations could be * set to a large value. */ CHECK_FOR_INTERRUPTS(); #endif if (pg_hmac_init(hmac_ctx, (uint8 *)password, password_len) < 0 || pg_hmac_update(hmac_ctx, (uint8 *)Ui_prev, key_length) < 0 || pg_hmac_final(hmac_ctx, Ui, key_length) < 0) { *errstr = pg_hmac_error(hmac_ctx); pg_hmac_free(hmac_ctx); return -1; } for (j = 0; j < key_length; j++) { result[j] ^= Ui[j]; } memcpy(Ui_prev, Ui, key_length); } pg_hmac_free(hmac_ctx); return 0; } /* * Calculate hash for a NULL-terminated string. (The NULL terminator is * not included in the hash). Returns 0 on success, -1 on failure with *errstr * pointing to a message about the error details. */ int scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length, uint8 *result, const char **errstr) { pg_cryptohash_ctx *ctx; ctx = pg_cryptohash_create(hash_type); if (ctx == NULL) { *errstr = pg_cryptohash_error(NULL); /* returns OOM */ return -1; } if (pg_cryptohash_init(ctx) < 0 || pg_cryptohash_update(ctx, input, key_length) < 0 || pg_cryptohash_final(ctx, result, key_length) < 0) { *errstr = pg_cryptohash_error(ctx); pg_cryptohash_free(ctx); return -1; } pg_cryptohash_free(ctx); return 0; } /* * Calculate ClientKey. Returns 0 on success, -1 on failure with *errstr * pointing to a message about the error details. */ int scram_ClientKey(const uint8 *salted_password, pg_cryptohash_type hash_type, int key_length, uint8 *result, const char **errstr) { pg_hmac_ctx *ctx = pg_hmac_create(hash_type); if (ctx == NULL) { *errstr = pg_hmac_error(NULL); /* returns OOM */ return -1; } if (pg_hmac_init(ctx, salted_password, key_length) < 0 || pg_hmac_update(ctx, (uint8 *)"Client Key", strlen("Client Key")) < 0 || pg_hmac_final(ctx, result, key_length) < 0) { *errstr = pg_hmac_error(ctx); pg_hmac_free(ctx); return -1; } pg_hmac_free(ctx); return 0; } /* * Calculate ServerKey. Returns 0 on success, -1 on failure with *errstr * pointing to a message about the error details. */ int scram_ServerKey(const uint8 *salted_password, pg_cryptohash_type hash_type, int key_length, uint8 *result, const char **errstr) { pg_hmac_ctx *ctx = pg_hmac_create(hash_type); if (ctx == NULL) { *errstr = pg_hmac_error(NULL); /* returns OOM */ return -1; } if (pg_hmac_init(ctx, salted_password, key_length) < 0 || pg_hmac_update(ctx, (uint8 *)"Server Key", strlen("Server Key")) < 0 || pg_hmac_final(ctx, result, key_length) < 0) { *errstr = pg_hmac_error(ctx); pg_hmac_free(ctx); return -1; } pg_hmac_free(ctx); return 0; } /* * Construct a SCRAM secret, for storing in pg_authid.rolpassword. * * The password should already have been processed with SASLprep, if necessary! * * The result is palloc'd or malloc'd, so caller is responsible for freeing it. * * On error, returns NULL and sets *errstr to point to a message about the * error details. */ char *scram_build_secret(pg_cryptohash_type hash_type, int key_length, const uint8 *salt, int saltlen, int iterations, const char *password, const char **errstr) { uint8 salted_password[SCRAM_MAX_KEY_LEN]; uint8 stored_key[SCRAM_MAX_KEY_LEN]; uint8 server_key[SCRAM_MAX_KEY_LEN]; char *result; char *p; int maxlen; int encoded_salt_len; int encoded_stored_len; int encoded_server_len; int encoded_result; /* Only this hash method is supported currently */ Assert(hash_type == PG_SHA256); Assert(iterations > 0); /* Calculate StoredKey and ServerKey */ if (scram_SaltedPassword(password, hash_type, key_length, salt, saltlen, iterations, salted_password, errstr) < 0 || scram_ClientKey(salted_password, hash_type, key_length, stored_key, errstr) < 0 || scram_H(stored_key, hash_type, key_length, stored_key, errstr) < 0 || scram_ServerKey(salted_password, hash_type, key_length, server_key, errstr) < 0) { /* errstr is filled already here */ #ifdef FRONTEND return NULL; #else elog(ERROR, "could not calculate stored key and server key: %s", *errstr); #endif } /*---------- * The format is: * SCRAM-SHA-256$:$: *---------- */ encoded_salt_len = pg_b64_enc_len(saltlen); encoded_stored_len = pg_b64_enc_len(key_length); encoded_server_len = pg_b64_enc_len(key_length); maxlen = strlen("SCRAM-SHA-256") + 1 + 10 + 1 /* iteration count */ + encoded_salt_len + 1 /* Base64-encoded salt */ + encoded_stored_len + 1 /* Base64-encoded StoredKey */ + encoded_server_len + 1; /* Base64-encoded ServerKey */ #ifdef FRONTEND result = od_malloc(maxlen); if (!result) { *errstr = _("out of memory"); return NULL; } #else result = palloc(maxlen); #endif p = result + snprintf(result, maxlen, "SCRAM-SHA-256$%d:", iterations); /* salt */ encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len); if (encoded_result < 0) { *errstr = _("could not encode salt"); #ifdef FRONTEND od_free(result); return NULL; #else elog(ERROR, "%s", *errstr); #endif } p += encoded_result; *(p++) = '$'; /* stored key */ encoded_result = pg_b64_encode(stored_key, key_length, p, encoded_stored_len); if (encoded_result < 0) { *errstr = _("could not encode stored key"); #ifdef FRONTEND od_free(result); return NULL; #else elog(ERROR, "%s", *errstr); #endif } p += encoded_result; *(p++) = ':'; /* server key */ encoded_result = pg_b64_encode(server_key, key_length, p, encoded_server_len); if (encoded_result < 0) { *errstr = _("could not encode server key"); #ifdef FRONTEND od_free(result); return NULL; #else elog(ERROR, "%s", *errstr); #endif } p += encoded_result; *(p++) = '\0'; Assert(p - result <= maxlen); return result; } odyssey-1.6.0-rc1/sources/common/string.c000066400000000000000000000071531526064202200203320ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * string.c * string handling helpers * * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/common/string.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #include "postgres.h" #else #include "postgres_fe.h" #endif #include "common/string.h" /* * Returns whether the string `str' has the postfix `end'. */ bool pg_str_endswith(const char *str, const char *end) { size_t slen = strlen(str); size_t elen = strlen(end); /* can't be a postfix if longer */ if (elen > slen) { return false; } /* compare the end of the strings */ str += slen - elen; return strcmp(str, end) == 0; } /* * strtoint --- just like strtol, but returns int not long */ int strtoint(const char *restrict str, char **restrict endptr, int base) { long val; val = strtol(str, endptr, base); if (val != (int)val) { errno = ERANGE; } return (int)val; } /* * pg_clean_ascii -- Replace any non-ASCII chars with a "\xXX" string * * Makes a newly allocated copy of the string passed in, which must be * '\0'-terminated. In the backend, additional alloc_flags may be provided and * will be passed as-is to palloc_extended(); in the frontend, alloc_flags is * ignored and the copy is malloc'd. * * This function exists specifically to deal with filtering out * non-ASCII characters in a few places where the client can provide an almost * arbitrary string (and it isn't checked to ensure it's a valid username or * database name or similar) and we don't want to have control characters or other * things ending up in the log file where server admins might end up with a * messed up terminal when looking at them. * * In general, this function should NOT be used- instead, consider how to handle * the string without needing to filter out the non-ASCII characters. * * Ultimately, we'd like to improve the situation to not require replacing all * non-ASCII but perform more intelligent filtering which would allow UTF or * similar, but it's unclear exactly what we should allow, so stick to ASCII only * for now. */ char *pg_clean_ascii(const char *str, int alloc_flags) { (void)alloc_flags; size_t dstlen; char *dst; const char *p; size_t i = 0; /* Worst case, each byte can become four bytes, plus a null terminator. */ dstlen = strlen(str) * 4 + 1; #ifdef FRONTEND dst = od_malloc(dstlen); #else dst = palloc_extended(dstlen, alloc_flags); #endif if (!dst) { return NULL; } for (p = str; *p != '\0'; p++) { /* Only allow clean ASCII chars in the string */ if (*p < 32 || *p > 126) { Assert(i < (dstlen - 3)); snprintf(&dst[i], dstlen - i, "\\x%02x", (unsigned char)*p); i += 4; } else { Assert(i < dstlen); dst[i] = *p; i++; } } Assert(i < dstlen); dst[i] = '\0'; return dst; } /* * pg_is_ascii -- Check if string is made only of ASCII characters */ bool pg_is_ascii(const char *str) { while (*str) { if (IS_HIGHBIT_SET(*str)) { return false; } str++; } return true; } /* * pg_strip_crlf -- Remove any trailing newline and carriage return * * Removes any trailing newline and carriage return characters (\r on * Windows) in the input string, zero-terminating it. * * The passed in string must be zero-terminated. This function returns * the new length of the string. */ int pg_strip_crlf(char *str) { int len = strlen(str); while (len > 0 && (str[len - 1] == '\n' || str[len - 1] == '\r')) { str[--len] = '\0'; } return len; } odyssey-1.6.0-rc1/sources/common/unicode_norm.c000066400000000000000000000367121526064202200215100ustar00rootroot00000000000000/*------------------------------------------------------------------------- * unicode_norm.c * Normalize a Unicode string * * This implements Unicode normalization, per the documentation at * https://www.unicode.org/reports/tr15/. * * Portions Copyright (c) 2017-2025, PostgreSQL Global Development Group * * IDENTIFICATION * src/common/unicode_norm.c * *------------------------------------------------------------------------- */ #ifndef FRONTEND #include "postgres.h" #else #include "postgres_fe.h" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wsign-compare" #endif #include "common/unicode_norm.h" #ifndef FRONTEND #include "common/unicode_norm_hashfunc.h" #include "common/unicode_normprops_table.h" #include "port/pg_bswap.h" #else #include "common/unicode_norm_table.h" #endif #ifndef FRONTEND #define ALLOC(size) palloc(size) #define FREE(size) pfree(size) #else #define ALLOC(size) od_malloc(size) #define FREE(size) od_free(size) #endif /* Constants for calculations with Hangul characters */ #define SBASE 0xAC00 /* U+AC00 */ #define LBASE 0x1100 /* U+1100 */ #define VBASE 0x1161 /* U+1161 */ #define TBASE 0x11A7 /* U+11A7 */ #define LCOUNT 19 #define VCOUNT 21 #define TCOUNT 28 #define NCOUNT VCOUNT *TCOUNT #define SCOUNT LCOUNT *NCOUNT #ifdef FRONTEND /* comparison routine for bsearch() of decomposition lookup table. */ static int conv_compare(const void *p1, const void *p2) { uint32 v1, v2; v1 = *(const uint32 *)p1; v2 = ((const pg_unicode_decomposition *)p2)->codepoint; return (v1 > v2) ? 1 : ((v1 == v2) ? 0 : -1); } #endif /* * get_code_entry * * Get the entry corresponding to code in the decomposition lookup table. * The backend version of this code uses a perfect hash function for the * lookup, while the frontend version uses a binary search. */ static const pg_unicode_decomposition *get_code_entry(char32_t code) { #ifndef FRONTEND int h; uint32 hashkey; pg_unicode_decompinfo decompinfo = UnicodeDecompInfo; /* * Compute the hash function. The hash key is the codepoint with the bytes * in network order. */ hashkey = pg_hton32(code); h = decompinfo.hash(&hashkey); /* An out-of-range result implies no match */ if (h < 0 || h >= decompinfo.num_decomps) { return NULL; } /* * Since it's a perfect hash, we need only match to the specific codepoint * it identifies. */ if (code != decompinfo.decomps[h].codepoint) { return NULL; } /* Success! */ return &decompinfo.decomps[h]; #else return bsearch(&(code), UnicodeDecompMain, lengthof(UnicodeDecompMain), sizeof(pg_unicode_decomposition), conv_compare); #endif } /* * Get the combining class of the given codepoint. */ static uint8 get_canonical_class(char32_t code) { const pg_unicode_decomposition *entry = get_code_entry(code); /* * If no entries are found, the character used is either an Hangul * character or a character with a class of 0 and no decompositions. */ if (!entry) { return 0; } else { return entry->comb_class; } } /* * Given a decomposition entry looked up earlier, get the decomposed * characters. * * Note: the returned pointer can point to statically allocated buffer, and * is only valid until next call to this function! */ static const char32_t * get_code_decomposition(const pg_unicode_decomposition *entry, int *dec_size) { static char32_t x; if (DECOMPOSITION_IS_INLINE(entry)) { Assert(DECOMPOSITION_SIZE(entry) == 1); x = (char32_t)entry->dec_index; *dec_size = 1; return &x; } else { *dec_size = DECOMPOSITION_SIZE(entry); return &UnicodeDecomp_codepoints[entry->dec_index]; } } /* * Calculate how many characters a given character will decompose to. * * This needs to recurse, if the character decomposes into characters that * are, in turn, decomposable. */ static int get_decomposed_size(char32_t code, bool compat) { const pg_unicode_decomposition *entry; int size = 0; int i; const uint32 *decomp; int dec_size; /* * Fast path for Hangul characters not stored in tables to save memory as * decomposition is algorithmic. See * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details * on the matter. */ if (code >= SBASE && code < SBASE + SCOUNT) { uint32 tindex, sindex; sindex = code - SBASE; tindex = sindex % TCOUNT; if (tindex != 0) { return 3; } return 2; } entry = get_code_entry(code); /* * Just count current code if no other decompositions. A NULL entry is * equivalent to a character with class 0 and no decompositions. */ if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 || (!compat && DECOMPOSITION_IS_COMPAT(entry))) { return 1; } /* * If this entry has other decomposition codes look at them as well. First * get its decomposition in the list of tables available. */ decomp = get_code_decomposition(entry, &dec_size); for (i = 0; i < dec_size; i++) { uint32 lcode = decomp[i]; size += get_decomposed_size(lcode, compat); } return size; } /* * Recompose a set of characters. For hangul characters, the calculation * is algorithmic. For others, an inverse lookup at the decomposition * table is necessary. Returns true if a recomposition can be done, and * false otherwise. */ static bool recompose_code(uint32 start, uint32 code, uint32 *result) { /* * Handle Hangul characters algorithmically, per the Unicode spec. * * Check if two current characters are L and V. */ if (start >= LBASE && start < LBASE + LCOUNT && code >= VBASE && code < VBASE + VCOUNT) { /* make syllable of form LV */ uint32 lindex = start - LBASE; uint32 vindex = code - VBASE; *result = SBASE + (lindex * VCOUNT + vindex) * TCOUNT; return true; } /* Check if two current characters are LV and T */ else if (start >= SBASE && start < (SBASE + SCOUNT) && ((start - SBASE) % TCOUNT) == 0 && code >= TBASE && code < (TBASE + TCOUNT)) { /* make syllable of form LVT */ uint32 tindex = code - TBASE; *result = start + tindex; return true; } else { const pg_unicode_decomposition *entry; /* * Do an inverse lookup of the decomposition tables to see if anything * matches. The comparison just needs to be a perfect match on the * sub-table of size two, because the start character has already been * recomposed partially. This lookup uses a perfect hash function for * the backend code. */ #ifndef FRONTEND int h, inv_lookup_index; uint64 hashkey; pg_unicode_recompinfo recompinfo = UnicodeRecompInfo; /* * Compute the hash function. The hash key is formed by concatenating * bytes of the two codepoints in network order. See also * src/common/unicode/generate-unicode_norm_table.pl. */ hashkey = pg_hton64(((uint64)start << 32) | (uint64)code); h = recompinfo.hash(&hashkey); /* An out-of-range result implies no match */ if (h < 0 || h >= recompinfo.num_recomps) { return false; } inv_lookup_index = recompinfo.inverse_lookup[h]; entry = &UnicodeDecompMain[inv_lookup_index]; if (start == UnicodeDecomp_codepoints[entry->dec_index] && code == UnicodeDecomp_codepoints[entry->dec_index + 1]) { *result = entry->codepoint; return true; } #else int i; for (i = 0; i < lengthof(UnicodeDecompMain); i++) { entry = &UnicodeDecompMain[i]; if (DECOMPOSITION_SIZE(entry) != 2) { continue; } if (DECOMPOSITION_NO_COMPOSE(entry)) { continue; } if (start == UnicodeDecomp_codepoints[entry->dec_index] && code == UnicodeDecomp_codepoints[entry->dec_index + 1]) { *result = entry->codepoint; return true; } } #endif /* !FRONTEND */ } return false; } /* * Decompose the given code into the array given by caller. The * decomposition begins at the position given by caller, saving one * lookup on the decomposition table. The current position needs to be * updated here to let the caller know from where to continue filling * in the array result. */ static void decompose_code(char32_t code, bool compat, char32_t **result, int *current) { const pg_unicode_decomposition *entry; int i; const uint32 *decomp; int dec_size; /* * Fast path for Hangul characters not stored in tables to save memory as * decomposition is algorithmic. See * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details * on the matter. */ if (code >= SBASE && code < SBASE + SCOUNT) { uint32 l, v, tindex, sindex; char32_t *res = *result; sindex = code - SBASE; l = LBASE + sindex / (VCOUNT * TCOUNT); v = VBASE + (sindex % (VCOUNT * TCOUNT)) / TCOUNT; tindex = sindex % TCOUNT; res[*current] = l; (*current)++; res[*current] = v; (*current)++; if (tindex != 0) { res[*current] = TBASE + tindex; (*current)++; } return; } entry = get_code_entry(code); /* * Just fill in with the current decomposition if there are no * decomposition codes to recurse to. A NULL entry is equivalent to a * character with class 0 and no decompositions, so just leave also in * this case. */ if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 || (!compat && DECOMPOSITION_IS_COMPAT(entry))) { char32_t *res = *result; res[*current] = code; (*current)++; return; } /* * If this entry has other decomposition codes look at them as well. */ decomp = get_code_decomposition(entry, &dec_size); for (i = 0; i < dec_size; i++) { char32_t lcode = (char32_t)decomp[i]; /* Leave if no more decompositions */ decompose_code(lcode, compat, result, current); } } /* * unicode_normalize - Normalize a Unicode string to the specified form. * * The input is a 0-terminated array of codepoints. * * In frontend, returns a 0-terminated array of codepoints, allocated with * malloc. Or NULL if we run out of memory. In backend, the returned * string is palloc'd instead, and OOM is reported with ereport(). */ char32_t *unicode_normalize(UnicodeNormalizationForm form, const char32_t *input) { bool compat = (form == UNICODE_NFKC || form == UNICODE_NFKD); bool recompose = (form == UNICODE_NFC || form == UNICODE_NFKC); char32_t *decomp_chars; char32_t *recomp_chars; int decomp_size, current_size; int count; const char32_t *p; /* variables for recomposition */ int last_class; int starter_pos; int target_pos; uint32 starter_ch; /* First, do character decomposition */ /* * Calculate how many characters long the decomposed version will be. */ decomp_size = 0; for (p = input; *p; p++) { decomp_size += get_decomposed_size(*p, compat); } decomp_chars = (char32_t *)ALLOC((decomp_size + 1) * sizeof(char32_t)); if (decomp_chars == NULL) { return NULL; } /* * Now fill in each entry recursively. This needs a second pass on the * decomposition table. */ current_size = 0; for (p = input; *p; p++) { decompose_code(*p, compat, &decomp_chars, ¤t_size); } decomp_chars[decomp_size] = '\0'; Assert(decomp_size == current_size); /* Leave if there is nothing to decompose */ if (decomp_size == 0) { return decomp_chars; } /* * Now apply canonical ordering. */ for (count = 1; count < decomp_size; count++) { char32_t prev = decomp_chars[count - 1]; char32_t next = decomp_chars[count]; char32_t tmp; const uint8 prevClass = get_canonical_class(prev); const uint8 nextClass = get_canonical_class(next); /* * Per Unicode (https://www.unicode.org/reports/tr15/tr15-18.html) * annex 4, a sequence of two adjacent characters in a string is an * exchangeable pair if the combining class (from the Unicode * Character Database) for the first character is greater than the * combining class for the second, and the second is not a starter. A * character is a starter if its combining class is 0. */ if (prevClass == 0 || nextClass == 0) { continue; } if (prevClass <= nextClass) { continue; } /* exchange can happen */ tmp = decomp_chars[count - 1]; decomp_chars[count - 1] = decomp_chars[count]; decomp_chars[count] = tmp; /* backtrack to check again */ if (count > 1) { count -= 2; } } if (!recompose) { return decomp_chars; } /* * The last phase of NFC and NFKC is the recomposition of the reordered * Unicode string using combining classes. The recomposed string cannot be * longer than the decomposed one, so make the allocation of the output * string based on that assumption. */ recomp_chars = (char32_t *)ALLOC((decomp_size + 1) * sizeof(char32_t)); if (!recomp_chars) { FREE(decomp_chars); return NULL; } last_class = -1; /* this eliminates a special check */ starter_pos = 0; target_pos = 1; starter_ch = recomp_chars[0] = decomp_chars[0]; for (count = 1; count < decomp_size; count++) { char32_t ch = decomp_chars[count]; int ch_class = get_canonical_class(ch); char32_t composite; if (last_class < ch_class && recompose_code(starter_ch, ch, &composite)) { recomp_chars[starter_pos] = composite; starter_ch = composite; } else if (ch_class == 0) { starter_pos = target_pos; starter_ch = ch; last_class = -1; recomp_chars[target_pos++] = ch; } else { last_class = ch_class; recomp_chars[target_pos++] = ch; } } recomp_chars[target_pos] = (char32_t)'\0'; FREE(decomp_chars); return recomp_chars; } /* * Normalization "quick check" algorithm; see * */ /* We only need this in the backend. */ #ifndef FRONTEND static const pg_unicode_normprops * qc_hash_lookup(char32_t ch, const pg_unicode_norminfo *norminfo) { int h; uint32 hashkey; /* * Compute the hash function. The hash key is the codepoint with the bytes * in network order. */ hashkey = pg_hton32(ch); h = norminfo->hash(&hashkey); /* An out-of-range result implies no match */ if (h < 0 || h >= norminfo->num_normprops) { return NULL; } /* * Since it's a perfect hash, we need only match to the specific codepoint * it identifies. */ if (ch != norminfo->normprops[h].codepoint) { return NULL; } /* Success! */ return &norminfo->normprops[h]; } /* * Look up the normalization quick check character property */ static UnicodeNormalizationQC qc_is_allowed(UnicodeNormalizationForm form, char32_t ch) { const pg_unicode_normprops *found = NULL; switch (form) { case UNICODE_NFC: found = qc_hash_lookup(ch, &UnicodeNormInfo_NFC_QC); break; case UNICODE_NFKC: found = qc_hash_lookup(ch, &UnicodeNormInfo_NFKC_QC); break; default: Assert(false); break; } if (found) { return found->quickcheck; } else { return UNICODE_NORM_QC_YES; } } UnicodeNormalizationQC unicode_is_normalized_quickcheck(UnicodeNormalizationForm form, const char32_t *input) { uint8 lastCanonicalClass = 0; UnicodeNormalizationQC result = UNICODE_NORM_QC_YES; /* * For the "D" forms, we don't run the quickcheck. We don't include the * lookup tables for those because they are huge, checking for these * particular forms is less common, and running the slow path is faster * for the "D" forms than the "C" forms because you don't need to * recompose, which is slow. */ if (form == UNICODE_NFD || form == UNICODE_NFKD) { return UNICODE_NORM_QC_MAYBE; } for (const char32_t *p = input; *p; p++) { char32_t ch = *p; uint8 canonicalClass; UnicodeNormalizationQC check; canonicalClass = get_canonical_class(ch); if (lastCanonicalClass > canonicalClass && canonicalClass != 0) { return UNICODE_NORM_QC_NO; } check = qc_is_allowed(form, ch); if (check == UNICODE_NORM_QC_NO) { return UNICODE_NORM_QC_NO; } else if (check == UNICODE_NORM_QC_MAYBE) { result = UNICODE_NORM_QC_MAYBE; } lastCanonicalClass = canonicalClass; } return result; } #endif /* !FRONTEND */ odyssey-1.6.0-rc1/sources/common/wchar.c000066400000000000000000001423221526064202200201260ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * wchar.c * Functions for working with multibyte characters in various encodings. * * Portions Copyright (c) 1998-2025, PostgreSQL Global Development Group * * IDENTIFICATION * src/common/wchar.c * *------------------------------------------------------------------------- */ #include "od_c.h" #include "pg_compat.h" #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wunused-parameter" #endif #include #include "mb/pg_wchar.h" #include "utils/ascii.h" /* * In today's multibyte encodings other than UTF8, this two-byte sequence * ensures pg_encoding_mblen() == 2 && pg_encoding_verifymbstr() == 0. * * For historical reasons, several verifychar implementations opt to reject * this pair specifically. Byte pair range constraints, in encoding * originator documentation, always excluded this pair. No core conversion * could translate it. However, longstanding verifychar implementations * accepted any non-NUL byte. big5_to_euc_tw and big5_to_mic even translate * pairs not valid per encoding originator documentation. To avoid tightening * core or non-core conversions in a security patch, we sought this one pair. * * PQescapeString() historically used spaces for BYTE1; many other values * could suffice for BYTE1. */ #define NONUTF8_INVALID_BYTE0 (0x8d) #define NONUTF8_INVALID_BYTE1 (' ') /* * Operations on multi-byte encodings are driven by a table of helper * functions. * * To add an encoding support, define mblen(), dsplen(), verifychar() and * verifystr() for the encoding. For server-encodings, also define mb2wchar() * and wchar2mb() conversion functions. * * These functions generally assume that their input is validly formed. * The "verifier" functions, further down in the file, have to be more * paranoid. * * We expect that mblen() does not need to examine more than the first byte * of the character to discover the correct length. GB18030 is an exception * to that rule, though, as it also looks at second byte. But even that * behaves in a predictable way, if you only pass the first byte: it will * treat 4-byte encoded characters as two 2-byte encoded characters, which is * good enough for all current uses. * * Note: for the display output of psql to work properly, the return values * of the dsplen functions must conform to the Unicode standard. In particular * the NUL character is zero width and control characters are generally * width -1. It is recommended that non-ASCII encodings refer their ASCII * subset to the ASCII routines to ensure consistency. */ /* * SQL/ASCII */ static int pg_ascii2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; while (len > 0 && *from) { *to++ = *from++; len--; cnt++; } *to = 0; return cnt; } static int pg_ascii_mblen(const unsigned char *s) { return 1; } static int pg_ascii_dsplen(const unsigned char *s) { if (*s == '\0') { return 0; } if (*s < 0x20 || *s == 0x7f) { return -1; } return 1; } /* * EUC */ static int pg_euc2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; while (len > 0 && *from) { if (*from == SS2 && len >= 2) /* JIS X 0201 (so called "1 byte * KANA") */ { from++; *to = (SS2 << 8) | *from++; len -= 2; } else if (*from == SS3 && len >= 3) /* JIS X 0212 KANJI */ { from++; *to = (SS3 << 16) | (*from++ << 8); *to |= *from++; len -= 3; } else if (IS_HIGHBIT_SET(*from) && len >= 2) /* JIS X 0208 KANJI */ { *to = *from++ << 8; *to |= *from++; len -= 2; } else /* must be ASCII */ { *to = *from++; len--; } to++; cnt++; } *to = 0; return cnt; } static inline int pg_euc_mblen(const unsigned char *s) { int len; if (*s == SS2) { len = 2; } else if (*s == SS3) { len = 3; } else if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = 1; } return len; } static inline int pg_euc_dsplen(const unsigned char *s) { int len; if (*s == SS2) { len = 2; } else if (*s == SS3) { len = 2; } else if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = pg_ascii_dsplen(s); } return len; } /* * EUC_JP */ static int pg_eucjp2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { return pg_euc2wchar_with_len(from, to, len); } static int pg_eucjp_mblen(const unsigned char *s) { return pg_euc_mblen(s); } static int pg_eucjp_dsplen(const unsigned char *s) { int len; if (*s == SS2) { len = 1; } else if (*s == SS3) { len = 2; } else if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = pg_ascii_dsplen(s); } return len; } /* * EUC_KR */ static int pg_euckr2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { return pg_euc2wchar_with_len(from, to, len); } static int pg_euckr_mblen(const unsigned char *s) { return pg_euc_mblen(s); } static int pg_euckr_dsplen(const unsigned char *s) { return pg_euc_dsplen(s); } /* * EUC_CN * */ static int pg_euccn2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; while (len > 0 && *from) { if (*from == SS2 && len >= 3) /* code set 2 (unused?) */ { from++; *to = (SS2 << 16) | (*from++ << 8); *to |= *from++; len -= 3; } else if (*from == SS3 && len >= 3) /* code set 3 (unused ?) */ { from++; *to = (SS3 << 16) | (*from++ << 8); *to |= *from++; len -= 3; } else if (IS_HIGHBIT_SET(*from) && len >= 2) /* code set 1 */ { *to = *from++ << 8; *to |= *from++; len -= 2; } else { *to = *from++; len--; } to++; cnt++; } *to = 0; return cnt; } static int pg_euccn_mblen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = 1; } return len; } static int pg_euccn_dsplen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = pg_ascii_dsplen(s); } return len; } /* * EUC_TW * */ static int pg_euctw2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; while (len > 0 && *from) { if (*from == SS2 && len >= 4) /* code set 2 */ { from++; *to = (((uint32)SS2) << 24) | (*from++ << 16); *to |= *from++ << 8; *to |= *from++; len -= 4; } else if (*from == SS3 && len >= 3) /* code set 3 (unused?) */ { from++; *to = (SS3 << 16) | (*from++ << 8); *to |= *from++; len -= 3; } else if (IS_HIGHBIT_SET(*from) && len >= 2) /* code set 2 */ { *to = *from++ << 8; *to |= *from++; len -= 2; } else { *to = *from++; len--; } to++; cnt++; } *to = 0; return cnt; } static int pg_euctw_mblen(const unsigned char *s) { int len; if (*s == SS2) { len = 4; } else if (*s == SS3) { len = 3; } else if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = 1; } return len; } static int pg_euctw_dsplen(const unsigned char *s) { int len; if (*s == SS2) { len = 2; } else if (*s == SS3) { len = 2; } else if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = pg_ascii_dsplen(s); } return len; } /* * Convert pg_wchar to EUC_* encoding. * caller must allocate enough space for "to", including a trailing zero! * len: length of from. * "from" not necessarily null terminated. */ static int pg_wchar2euc_with_len(const pg_wchar *from, unsigned char *to, int len) { int cnt = 0; while (len > 0 && *from) { unsigned char c; if ((c = (*from >> 24))) { *to++ = c; *to++ = (*from >> 16) & 0xff; *to++ = (*from >> 8) & 0xff; *to++ = *from & 0xff; cnt += 4; } else if ((c = (*from >> 16))) { *to++ = c; *to++ = (*from >> 8) & 0xff; *to++ = *from & 0xff; cnt += 3; } else if ((c = (*from >> 8))) { *to++ = c; *to++ = *from & 0xff; cnt += 2; } else { *to++ = *from; cnt++; } from++; len--; } *to = 0; return cnt; } /* * JOHAB */ static int pg_johab_mblen(const unsigned char *s) { return pg_euc_mblen(s); } static int pg_johab_dsplen(const unsigned char *s) { return pg_euc_dsplen(s); } /* * convert UTF8 string to pg_wchar (UCS-4) * caller must allocate enough space for "to", including a trailing zero! * len: length of from. * "from" not necessarily null terminated. */ static int pg_utf2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; uint32 c1, c2, c3, c4; while (len > 0 && *from) { if ((*from & 0x80) == 0) { *to = *from++; len--; } else if ((*from & 0xe0) == 0xc0) { if (len < 2) { break; /* drop trailing incomplete char */ } c1 = *from++ & 0x1f; c2 = *from++ & 0x3f; *to = (c1 << 6) | c2; len -= 2; } else if ((*from & 0xf0) == 0xe0) { if (len < 3) { break; /* drop trailing incomplete char */ } c1 = *from++ & 0x0f; c2 = *from++ & 0x3f; c3 = *from++ & 0x3f; *to = (c1 << 12) | (c2 << 6) | c3; len -= 3; } else if ((*from & 0xf8) == 0xf0) { if (len < 4) { break; /* drop trailing incomplete char */ } c1 = *from++ & 0x07; c2 = *from++ & 0x3f; c3 = *from++ & 0x3f; c4 = *from++ & 0x3f; *to = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4; len -= 4; } else { /* treat a bogus char as length 1; not ours to raise error */ *to = *from++; len--; } to++; cnt++; } *to = 0; return cnt; } /* * Trivial conversion from pg_wchar to UTF-8. * caller should allocate enough space for "to" * len: length of from. * "from" not necessarily null terminated. */ static int pg_wchar2utf_with_len(const pg_wchar *from, unsigned char *to, int len) { int cnt = 0; while (len > 0 && *from) { int char_len; unicode_to_utf8(*from, to); char_len = pg_utf_mblen(to); cnt += char_len; to += char_len; from++; len--; } *to = 0; return cnt; } /* * Return the byte length of a UTF8 character pointed to by s * * Note: in the current implementation we do not support UTF8 sequences * of more than 4 bytes; hence do NOT return a value larger than 4. * We return "1" for any leading byte that is either flat-out illegal or * indicates a length larger than we support. * * pg_utf2wchar_with_len(), utf8_to_unicode(), pg_utf8_islegal(), and perhaps * other places would need to be fixed to change this. */ int pg_utf_mblen(const unsigned char *s) { int len; if ((*s & 0x80) == 0) { len = 1; } else if ((*s & 0xe0) == 0xc0) { len = 2; } else if ((*s & 0xf0) == 0xe0) { len = 3; } else if ((*s & 0xf8) == 0xf0) { len = 4; } #ifdef NOT_USED else if ((*s & 0xfc) == 0xf8) { len = 5; } else if ((*s & 0xfe) == 0xfc) { len = 6; } #endif else { len = 1; } return len; } /* * This is an implementation of wcwidth() and wcswidth() as defined in * "The Single UNIX Specification, Version 2, The Open Group, 1997" * * * Markus Kuhn -- 2001-09-08 -- public domain * * customised for PostgreSQL * * original available at : http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c */ struct mbinterval { unsigned int first; unsigned int last; }; /* auxiliary function for binary search in interval table */ static int mbbisearch(pg_wchar ucs, const struct mbinterval *table, int max) { int min = 0; int mid; if (ucs < table[0].first || ucs > table[max].last) { return 0; } while (max >= min) { mid = (min + max) / 2; if (ucs > table[mid].last) { min = mid + 1; } else if (ucs < table[mid].first) { max = mid - 1; } else { return 1; } } return 0; } /* The following functions define the column width of an ISO 10646 * character as follows: * * - The null character (U+0000) has a column width of 0. * * - Other C0/C1 control characters and DEL will lead to a return * value of -1. * * - Non-spacing and enclosing combining characters (general * category code Mn, Me or Cf in the Unicode database) have a * column width of 0. * * - Spacing characters in the East Asian Wide (W) or East Asian * FullWidth (F) category as defined in Unicode Technical * Report #11 have a column width of 2. * * - All remaining characters (including all printable * ISO 8859-1 and WGL4 characters, Unicode control characters, * etc.) have a column width of 1. * * This implementation assumes that wchar_t characters are encoded * in ISO 10646. */ static int ucs_wcwidth(pg_wchar ucs) { #include "common/unicode_nonspacing_table.h" #include "common/unicode_east_asian_fw_table.h" /* test for 8-bit control characters */ if (ucs == 0) { return 0; } if (ucs < 0x20 || (ucs >= 0x7f && ucs < 0xa0) || ucs > 0x0010ffff) { return -1; } /* * binary search in table of non-spacing characters * * XXX: In the official Unicode sources, it is possible for a character to * be described as both non-spacing and wide at the same time. As of * Unicode 13.0, treating the non-spacing property as the determining * factor for display width leads to the correct behavior, so do that * search first. */ if (mbbisearch(ucs, nonspacing, sizeof(nonspacing) / sizeof(struct mbinterval) - 1)) { return 0; } /* binary search in table of wide characters */ if (mbbisearch(ucs, east_asian_fw, sizeof(east_asian_fw) / sizeof(struct mbinterval) - 1)) { return 2; } return 1; } static int pg_utf_dsplen(const unsigned char *s) { return ucs_wcwidth(utf8_to_unicode(s)); } /* * convert mule internal code to pg_wchar * caller should allocate enough space for "to" * len: length of from. * "from" not necessarily null terminated. */ static int pg_mule2wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; while (len > 0 && *from) { if (IS_LC1(*from) && len >= 2) { *to = *from++ << 16; *to |= *from++; len -= 2; } else if (IS_LCPRV1(*from) && len >= 3) { from++; *to = *from++ << 16; *to |= *from++; len -= 3; } else if (IS_LC2(*from) && len >= 3) { *to = *from++ << 16; *to |= *from++ << 8; *to |= *from++; len -= 3; } else if (IS_LCPRV2(*from) && len >= 4) { from++; *to = *from++ << 16; *to |= *from++ << 8; *to |= *from++; len -= 4; } else { /* assume ASCII */ *to = (unsigned char)*from++; len--; } to++; cnt++; } *to = 0; return cnt; } /* * convert pg_wchar to mule internal code * caller should allocate enough space for "to" * len: length of from. * "from" not necessarily null terminated. */ static int pg_wchar2mule_with_len(const pg_wchar *from, unsigned char *to, int len) { int cnt = 0; while (len > 0 && *from) { unsigned char lb; lb = (*from >> 16) & 0xff; if (IS_LC1(lb)) { *to++ = lb; *to++ = *from & 0xff; cnt += 2; } else if (IS_LC2(lb)) { *to++ = lb; *to++ = (*from >> 8) & 0xff; *to++ = *from & 0xff; cnt += 3; } else if (IS_LCPRV1_A_RANGE(lb)) { *to++ = LCPRV1_A; *to++ = lb; *to++ = *from & 0xff; cnt += 3; } else if (IS_LCPRV1_B_RANGE(lb)) { *to++ = LCPRV1_B; *to++ = lb; *to++ = *from & 0xff; cnt += 3; } else if (IS_LCPRV2_A_RANGE(lb)) { *to++ = LCPRV2_A; *to++ = lb; *to++ = (*from >> 8) & 0xff; *to++ = *from & 0xff; cnt += 4; } else if (IS_LCPRV2_B_RANGE(lb)) { *to++ = LCPRV2_B; *to++ = lb; *to++ = (*from >> 8) & 0xff; *to++ = *from & 0xff; cnt += 4; } else { *to++ = *from & 0xff; cnt += 1; } from++; len--; } *to = 0; return cnt; } /* exported for direct use by conv.c */ int pg_mule_mblen(const unsigned char *s) { int len; if (IS_LC1(*s)) { len = 2; } else if (IS_LCPRV1(*s)) { len = 3; } else if (IS_LC2(*s)) { len = 3; } else if (IS_LCPRV2(*s)) { len = 4; } else { len = 1; /* assume ASCII */ } return len; } static int pg_mule_dsplen(const unsigned char *s) { int len; /* * Note: it's not really appropriate to assume that all multibyte charsets * are double-wide on screen. But this seems an okay approximation for * the MULE charsets we currently support. */ if (IS_LC1(*s)) { len = 1; } else if (IS_LCPRV1(*s)) { len = 1; } else if (IS_LC2(*s)) { len = 2; } else if (IS_LCPRV2(*s)) { len = 2; } else { len = 1; /* assume ASCII */ } return len; } /* * ISO8859-1 */ static int pg_latin12wchar_with_len(const unsigned char *from, pg_wchar *to, int len) { int cnt = 0; while (len > 0 && *from) { *to++ = *from++; len--; cnt++; } *to = 0; return cnt; } /* * Trivial conversion from pg_wchar to single byte encoding. Just ignores * high bits. * caller should allocate enough space for "to" * len: length of from. * "from" not necessarily null terminated. */ static int pg_wchar2single_with_len(const pg_wchar *from, unsigned char *to, int len) { int cnt = 0; while (len > 0 && *from) { *to++ = *from++; len--; cnt++; } *to = 0; return cnt; } static int pg_latin1_mblen(const unsigned char *s) { return 1; } static int pg_latin1_dsplen(const unsigned char *s) { return pg_ascii_dsplen(s); } /* * SJIS */ static int pg_sjis_mblen(const unsigned char *s) { int len; if (*s >= 0xa1 && *s <= 0xdf) { len = 1; /* 1 byte kana? */ } else if (IS_HIGHBIT_SET(*s)) { len = 2; /* kanji? */ } else { len = 1; /* should be ASCII */ } return len; } static int pg_sjis_dsplen(const unsigned char *s) { int len; if (*s >= 0xa1 && *s <= 0xdf) { len = 1; /* 1 byte kana? */ } else if (IS_HIGHBIT_SET(*s)) { len = 2; /* kanji? */ } else { len = pg_ascii_dsplen(s); /* should be ASCII */ } return len; } /* * Big5 */ static int pg_big5_mblen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; /* kanji? */ } else { len = 1; /* should be ASCII */ } return len; } static int pg_big5_dsplen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; /* kanji? */ } else { len = pg_ascii_dsplen(s); /* should be ASCII */ } return len; } /* * GBK */ static int pg_gbk_mblen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; /* kanji? */ } else { len = 1; /* should be ASCII */ } return len; } static int pg_gbk_dsplen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; /* kanji? */ } else { len = pg_ascii_dsplen(s); /* should be ASCII */ } return len; } /* * UHC */ static int pg_uhc_mblen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; /* 2byte? */ } else { len = 1; /* should be ASCII */ } return len; } static int pg_uhc_dsplen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; /* 2byte? */ } else { len = pg_ascii_dsplen(s); /* should be ASCII */ } return len; } /* * GB18030 * Added by Bill Huang , */ /* * Unlike all other mblen() functions, this also looks at the second byte of * the input. However, if you only pass the first byte of a multi-byte * string, and \0 as the second byte, this still works in a predictable way: * a 4-byte character will be reported as two 2-byte characters. That's * enough for all current uses, as a client-only encoding. It works that * way, because in any valid 4-byte GB18030-encoded character, the third and * fourth byte look like a 2-byte encoded character, when looked at * separately. */ static int pg_gb18030_mblen(const unsigned char *s) { int len; if (!IS_HIGHBIT_SET(*s)) { len = 1; /* ASCII */ } else if (*(s + 1) >= 0x30 && *(s + 1) <= 0x39) { len = 4; } else { len = 2; } return len; } static int pg_gb18030_dsplen(const unsigned char *s) { int len; if (IS_HIGHBIT_SET(*s)) { len = 2; } else { len = pg_ascii_dsplen(s); /* ASCII */ } return len; } /* *------------------------------------------------------------------- * multibyte sequence validators * * The verifychar functions accept "s", a pointer to the first byte of a * string, and "len", the remaining length of the string. If there is a * validly encoded character beginning at *s, return its length in bytes; * else return -1. * * The verifystr functions also accept "s", a pointer to a string and "len", * the length of the string. They verify the whole string, and return the * number of input bytes (<= len) that are valid. In other words, if the * whole string is valid, verifystr returns "len", otherwise it returns the * byte offset of the first invalid character. The verifystr functions must * test for and reject zeroes in the input. * * The verifychar functions can assume that len > 0 and that *s != '\0', but * they must test for and reject zeroes in any additional bytes of a * multibyte character. Note that this definition allows the function for a * single-byte encoding to be just "return 1". *------------------------------------------------------------------- */ static int pg_ascii_verifychar(const unsigned char *s, int len) { return 1; } static int pg_ascii_verifystr(const unsigned char *s, int len) { const unsigned char *nullpos = memchr(s, 0, len); if (nullpos == NULL) { return len; } else { return nullpos - s; } } #define IS_EUC_RANGE_VALID(c) ((c) >= 0xa1 && (c) <= 0xfe) static int pg_eucjp_verifychar(const unsigned char *s, int len) { int l; unsigned char c1, c2; c1 = *s++; switch (c1) { case SS2: /* JIS X 0201 */ l = 2; if (l > len) { return -1; } c2 = *s++; if (c2 < 0xa1 || c2 > 0xdf) { return -1; } break; case SS3: /* JIS X 0212 */ l = 3; if (l > len) { return -1; } c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } break; default: if (IS_HIGHBIT_SET(c1)) /* JIS X 0208? */ { l = 2; if (l > len) { return -1; } if (!IS_EUC_RANGE_VALID(c1)) { return -1; } c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } } else /* must be ASCII */ { l = 1; } break; } return l; } static int pg_eucjp_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_eucjp_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_euckr_verifychar(const unsigned char *s, int len) { int l; unsigned char c1, c2; c1 = *s++; if (IS_HIGHBIT_SET(c1)) { l = 2; if (l > len) { return -1; } if (!IS_EUC_RANGE_VALID(c1)) { return -1; } c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } } else /* must be ASCII */ { l = 1; } return l; } static int pg_euckr_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_euckr_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } /* EUC-CN byte sequences are exactly same as EUC-KR */ #define pg_euccn_verifychar pg_euckr_verifychar #define pg_euccn_verifystr pg_euckr_verifystr static int pg_euctw_verifychar(const unsigned char *s, int len) { int l; unsigned char c1, c2; c1 = *s++; switch (c1) { case SS2: /* CNS 11643 Plane 1-7 */ l = 4; if (l > len) { return -1; } c2 = *s++; if (c2 < 0xa1 || c2 > 0xa7) { return -1; } c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } break; case SS3: /* unused */ return -1; default: if (IS_HIGHBIT_SET(c1)) /* CNS 11643 Plane 1 */ { l = 2; if (l > len) { return -1; } /* no further range check on c1? */ c2 = *s++; if (!IS_EUC_RANGE_VALID(c2)) { return -1; } } else /* must be ASCII */ { l = 1; } break; } return l; } static int pg_euctw_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_euctw_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_johab_verifychar(const unsigned char *s, int len) { int l, mbl; unsigned char c; l = mbl = pg_johab_mblen(s); if (len < l) { return -1; } if (!IS_HIGHBIT_SET(*s)) { return mbl; } while (--l > 0) { c = *++s; if (!IS_EUC_RANGE_VALID(c)) { return -1; } } return mbl; } static int pg_johab_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_johab_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_mule_verifychar(const unsigned char *s, int len) { int l, mbl; unsigned char c; l = mbl = pg_mule_mblen(s); if (len < l) { return -1; } while (--l > 0) { c = *++s; if (!IS_HIGHBIT_SET(c)) { return -1; } } return mbl; } static int pg_mule_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_mule_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_latin1_verifychar(const unsigned char *s, int len) { return 1; } static int pg_latin1_verifystr(const unsigned char *s, int len) { const unsigned char *nullpos = memchr(s, 0, len); if (nullpos == NULL) { return len; } else { return nullpos - s; } } static int pg_sjis_verifychar(const unsigned char *s, int len) { int l, mbl; unsigned char c1, c2; l = mbl = pg_sjis_mblen(s); if (len < l) { return -1; } if (l == 1) { /* pg_sjis_mblen already verified it */ return mbl; } c1 = *s++; c2 = *s; if (!ISSJISHEAD(c1) || !ISSJISTAIL(c2)) { return -1; } return mbl; } static int pg_sjis_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_sjis_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_big5_verifychar(const unsigned char *s, int len) { int l, mbl; l = mbl = pg_big5_mblen(s); if (len < l) { return -1; } if (l == 2 && s[0] == NONUTF8_INVALID_BYTE0 && s[1] == NONUTF8_INVALID_BYTE1) { return -1; } while (--l > 0) { if (*++s == '\0') { return -1; } } return mbl; } static int pg_big5_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_big5_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_gbk_verifychar(const unsigned char *s, int len) { int l, mbl; l = mbl = pg_gbk_mblen(s); if (len < l) { return -1; } if (l == 2 && s[0] == NONUTF8_INVALID_BYTE0 && s[1] == NONUTF8_INVALID_BYTE1) { return -1; } while (--l > 0) { if (*++s == '\0') { return -1; } } return mbl; } static int pg_gbk_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_gbk_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_uhc_verifychar(const unsigned char *s, int len) { int l, mbl; l = mbl = pg_uhc_mblen(s); if (len < l) { return -1; } if (l == 2 && s[0] == NONUTF8_INVALID_BYTE0 && s[1] == NONUTF8_INVALID_BYTE1) { return -1; } while (--l > 0) { if (*++s == '\0') { return -1; } } return mbl; } static int pg_uhc_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_uhc_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_gb18030_verifychar(const unsigned char *s, int len) { int l; if (!IS_HIGHBIT_SET(*s)) { l = 1; /* ASCII */ } else if (len >= 4 && *(s + 1) >= 0x30 && *(s + 1) <= 0x39) { /* Should be 4-byte, validate remaining bytes */ if (*s >= 0x81 && *s <= 0xfe && *(s + 2) >= 0x81 && *(s + 2) <= 0xfe && *(s + 3) >= 0x30 && *(s + 3) <= 0x39) { l = 4; } else { l = -1; } } else if (len >= 2 && *s >= 0x81 && *s <= 0xfe) { /* Should be 2-byte, validate */ if ((*(s + 1) >= 0x40 && *(s + 1) <= 0x7e) || (*(s + 1) >= 0x80 && *(s + 1) <= 0xfe)) { l = 2; } else { l = -1; } } else { l = -1; } return l; } static int pg_gb18030_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_gb18030_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } static int pg_utf8_verifychar(const unsigned char *s, int len) { int l; if ((*s & 0x80) == 0) { if (*s == '\0') { return -1; } return 1; } else if ((*s & 0xe0) == 0xc0) { l = 2; } else if ((*s & 0xf0) == 0xe0) { l = 3; } else if ((*s & 0xf8) == 0xf0) { l = 4; } else { l = 1; } if (l > len) { return -1; } if (!pg_utf8_islegal(s, l)) { return -1; } return l; } /* * The fast path of the UTF-8 verifier uses a deterministic finite automaton * (DFA) for multibyte characters. In a traditional table-driven DFA, the * input byte and current state are used to compute an index into an array of * state transitions. Since the address of the next transition is dependent * on this computation, there is latency in executing the load instruction, * and the CPU is not kept busy. * * Instead, we use a "shift-based" DFA as described by Per Vognsen: * * https://gist.github.com/pervognsen/218ea17743e1442e59bb60d29b1aa725 * * In a shift-based DFA, the input byte is an index into array of integers * whose bit pattern encodes the state transitions. To compute the next * state, we simply right-shift the integer by the current state and apply a * mask. In this scheme, the address of the transition only depends on the * input byte, so there is better pipelining. * * The naming convention for states and transitions was adopted from a UTF-8 * to UTF-16/32 transcoder, whose table is reproduced below: * * https://github.com/BobSteagall/utf_utils/blob/6b7a465265de2f5fa6133d653df0c9bdd73bbcf8/src/utf_utils.cpp * * ILL ASC CR1 CR2 CR3 L2A L3A L3B L3C L4A L4B L4C CLASS / STATE * ========================================================================== * err, END, err, err, err, CS1, P3A, CS2, P3B, P4A, CS3, P4B, | BGN/END * err, err, err, err, err, err, err, err, err, err, err, err, | ERR * | * err, err, END, END, END, err, err, err, err, err, err, err, | CS1 * err, err, CS1, CS1, CS1, err, err, err, err, err, err, err, | CS2 * err, err, CS2, CS2, CS2, err, err, err, err, err, err, err, | CS3 * | * err, err, err, err, CS1, err, err, err, err, err, err, err, | P3A * err, err, CS1, CS1, err, err, err, err, err, err, err, err, | P3B * | * err, err, err, CS2, CS2, err, err, err, err, err, err, err, | P4A * err, err, CS2, err, err, err, err, err, err, err, err, err, | P4B * * In the most straightforward implementation, a shift-based DFA for UTF-8 * requires 64-bit integers to encode the transitions, but with an SMT solver * it's possible to find state numbers such that the transitions fit within * 32-bit integers, as Dougall Johnson demonstrated: * * https://gist.github.com/dougallj/166e326de6ad4cf2c94be97a204c025f * * This packed representation is the reason for the seemingly odd choice of * state values below. */ /* Error */ #define ERR 0 /* Begin */ #define BGN 11 /* Continuation states, expect 1/2/3 continuation bytes */ #define CS1 16 #define CS2 1 #define CS3 5 /* Partial states, where the first continuation byte has a restricted range */ #define P3A 6 /* Lead was E0, check for 3-byte overlong */ #define P3B 20 /* Lead was ED, check for surrogate */ #define P4A 25 /* Lead was F0, check for 4-byte overlong */ #define P4B 30 /* Lead was F4, check for too-large */ /* Begin and End are the same state */ #define END BGN /* the encoded state transitions for the lookup table */ /* ASCII */ #define ASC (END << BGN) /* 2-byte lead */ #define L2A (CS1 << BGN) /* 3-byte lead */ #define L3A (P3A << BGN) #define L3B (CS2 << BGN) #define L3C (P3B << BGN) /* 4-byte lead */ #define L4A (P4A << BGN) #define L4B (CS3 << BGN) #define L4C (P4B << BGN) /* continuation byte */ #define CR1 \ (END << CS1) | (CS1 << CS2) | (CS2 << CS3) | (CS1 << P3B) | (CS2 << P4B) #define CR2 \ (END << CS1) | (CS1 << CS2) | (CS2 << CS3) | (CS1 << P3B) | (CS2 << P4A) #define CR3 \ (END << CS1) | (CS1 << CS2) | (CS2 << CS3) | (CS1 << P3A) | (CS2 << P4A) /* invalid byte */ #define ILL ERR static const uint32 Utf8Transition[256] = { /* ASCII */ ILL, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, /* continuation bytes */ /* 80..8F */ CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, CR1, /* 90..9F */ CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, CR2, /* A0..BF */ CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, CR3, /* leading bytes */ /* C0..DF */ ILL, ILL, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, L2A, /* E0..EF */ L3A, L3B, L3B, L3B, L3B, L3B, L3B, L3B, L3B, L3B, L3B, L3B, L3B, L3C, L3B, L3B, /* F0..FF */ L4A, L4B, L4B, L4B, L4C, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL }; static void utf8_advance(const unsigned char *s, uint32 *state, int len) { /* Note: We deliberately don't check the state's value here. */ while (len > 0) { /* * It's important that the mask value is 31: In most instruction sets, * a shift by a 32-bit operand is understood to be a shift by its mod * 32, so the compiler should elide the mask operation. */ *state = Utf8Transition[*s++] >> (*state & 31); len--; } *state &= 31; } static int pg_utf8_verifystr(const unsigned char *s, int len) { const unsigned char *start = s; const int orig_len = len; uint32 state = BGN; /* * With a stride of two vector widths, gcc will unroll the loop. Even if * the compiler can unroll a longer loop, it's not worth it because we * must fall back to the byte-wise algorithm if we find any non-ASCII. */ #define STRIDE_LENGTH (2 * sizeof(Vector8)) if (len >= STRIDE_LENGTH) { while (len >= STRIDE_LENGTH) { /* * If the chunk is all ASCII, we can skip the full UTF-8 check, * but we must first check for a non-END state, which means the * previous chunk ended in the middle of a multibyte sequence. */ if (state != END || !is_valid_ascii(s, STRIDE_LENGTH)) { utf8_advance(s, &state, STRIDE_LENGTH); } s += STRIDE_LENGTH; len -= STRIDE_LENGTH; } /* The error state persists, so we only need to check for it here. */ if (state == ERR) { /* * Start over from the beginning with the slow path so we can * count the valid bytes. */ len = orig_len; s = start; } else if (state != END) { /* * The fast path exited in the middle of a multibyte sequence. * Walk backwards to find the leading byte so that the slow path * can resume checking from there. We must always backtrack at * least one byte, since the current byte could be e.g. an ASCII * byte after a 2-byte lead, which is invalid. */ do { Assert(s > start); s--; len++; Assert(IS_HIGHBIT_SET(*s)); } while (pg_utf_mblen(s) <= 1); } } /* check remaining bytes */ while (len > 0) { int l; /* fast path for ASCII-subset characters */ if (!IS_HIGHBIT_SET(*s)) { if (*s == '\0') { break; } l = 1; } else { l = pg_utf8_verifychar(s, len); if (l == -1) { break; } } s += l; len -= l; } return s - start; } /* * Check for validity of a single UTF-8 encoded character * * This directly implements the rules in RFC3629. The bizarre-looking * restrictions on the second byte are meant to ensure that there isn't * more than one encoding of a given Unicode character point; that is, * you may not use a longer-than-necessary byte sequence with high order * zero bits to represent a character that would fit in fewer bytes. * To do otherwise is to create security hazards (eg, create an apparent * non-ASCII character that decodes to plain ASCII). * * length is assumed to have been obtained by pg_utf_mblen(), and the * caller must have checked that that many bytes are present in the buffer. */ bool pg_utf8_islegal(const unsigned char *source, int length) { unsigned char a; switch (length) { default: /* reject lengths 5 and 6 for now */ return false; case 4: a = source[3]; if (a < 0x80 || a > 0xBF) { return false; } /* FALL THRU */ case 3: a = source[2]; if (a < 0x80 || a > 0xBF) { return false; } /* FALL THRU */ case 2: a = source[1]; switch (*source) { case 0xE0: if (a < 0xA0 || a > 0xBF) { return false; } break; case 0xED: if (a < 0x80 || a > 0x9F) { return false; } break; case 0xF0: if (a < 0x90 || a > 0xBF) { return false; } break; case 0xF4: if (a < 0x80 || a > 0x8F) { return false; } break; default: if (a < 0x80 || a > 0xBF) { return false; } break; } /* FALL THRU */ case 1: a = *source; if (a >= 0x80 && a < 0xC2) { return false; } if (a > 0xF4) { return false; } break; } return true; } /* * Fills the provided buffer with two bytes such that: * pg_encoding_mblen(dst) == 2 && pg_encoding_verifymbstr(dst) == 0 */ void pg_encoding_set_invalid(int encoding, char *dst) { Assert(pg_encoding_max_length(encoding) > 1); dst[0] = (encoding == PG_UTF8 ? 0xc0 : NONUTF8_INVALID_BYTE0); dst[1] = NONUTF8_INVALID_BYTE1; } /* *------------------------------------------------------------------- * encoding info table *------------------------------------------------------------------- */ const pg_wchar_tbl pg_wchar_table[] = { [PG_SQL_ASCII] = { pg_ascii2wchar_with_len, pg_wchar2single_with_len, pg_ascii_mblen, pg_ascii_dsplen, pg_ascii_verifychar, pg_ascii_verifystr, 1 }, [PG_EUC_JP] = { pg_eucjp2wchar_with_len, pg_wchar2euc_with_len, pg_eucjp_mblen, pg_eucjp_dsplen, pg_eucjp_verifychar, pg_eucjp_verifystr, 3 }, [PG_EUC_CN] = { pg_euccn2wchar_with_len, pg_wchar2euc_with_len, pg_euccn_mblen, pg_euccn_dsplen, pg_euccn_verifychar, pg_euccn_verifystr, 2 }, [PG_EUC_KR] = { pg_euckr2wchar_with_len, pg_wchar2euc_with_len, pg_euckr_mblen, pg_euckr_dsplen, pg_euckr_verifychar, pg_euckr_verifystr, 3 }, [PG_EUC_TW] = { pg_euctw2wchar_with_len, pg_wchar2euc_with_len, pg_euctw_mblen, pg_euctw_dsplen, pg_euctw_verifychar, pg_euctw_verifystr, 4 }, [PG_EUC_JIS_2004] = { pg_eucjp2wchar_with_len, pg_wchar2euc_with_len, pg_eucjp_mblen, pg_eucjp_dsplen, pg_eucjp_verifychar, pg_eucjp_verifystr, 3 }, [PG_UTF8] = { pg_utf2wchar_with_len, pg_wchar2utf_with_len, pg_utf_mblen, pg_utf_dsplen, pg_utf8_verifychar, pg_utf8_verifystr, 4 }, [PG_MULE_INTERNAL] = { pg_mule2wchar_with_len, pg_wchar2mule_with_len, pg_mule_mblen, pg_mule_dsplen, pg_mule_verifychar, pg_mule_verifystr, 4 }, [PG_LATIN1] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN2] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN3] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN4] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN5] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN6] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN7] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN8] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN9] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_LATIN10] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1256] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1258] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN866] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN874] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_KOI8R] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1251] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1252] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_ISO_8859_5] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_ISO_8859_6] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_ISO_8859_7] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_ISO_8859_8] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1250] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1253] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1254] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1255] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_WIN1257] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_KOI8U] = { pg_latin12wchar_with_len, pg_wchar2single_with_len, pg_latin1_mblen, pg_latin1_dsplen, pg_latin1_verifychar, pg_latin1_verifystr, 1 }, [PG_SJIS] = { 0, 0, pg_sjis_mblen, pg_sjis_dsplen, pg_sjis_verifychar, pg_sjis_verifystr, 2 }, [PG_BIG5] = { 0, 0, pg_big5_mblen, pg_big5_dsplen, pg_big5_verifychar, pg_big5_verifystr, 2 }, [PG_GBK] = { 0, 0, pg_gbk_mblen, pg_gbk_dsplen, pg_gbk_verifychar, pg_gbk_verifystr, 2 }, [PG_UHC] = { 0, 0, pg_uhc_mblen, pg_uhc_dsplen, pg_uhc_verifychar, pg_uhc_verifystr, 2 }, [PG_GB18030] = { 0, 0, pg_gb18030_mblen, pg_gb18030_dsplen, pg_gb18030_verifychar, pg_gb18030_verifystr, 4 }, [PG_JOHAB] = { 0, 0, pg_johab_mblen, pg_johab_dsplen, pg_johab_verifychar, pg_johab_verifystr, 3 }, [PG_SHIFT_JIS_2004] = { 0, 0, pg_sjis_mblen, pg_sjis_dsplen, pg_sjis_verifychar, pg_sjis_verifystr, 2 }, }; /* * Returns the byte length of a multibyte character. * * Choose "mblen" functions based on the input string characteristics. * pg_encoding_mblen() can be used when ANY of these conditions are met: * * - The input string is zero-terminated * * - The input string is known to be valid in the encoding (e.g., string * converted from database encoding) * * - The encoding is not GB18030 (e.g., when only database encodings are * passed to 'encoding' parameter) * * encoding==GB18030 requires examining up to two bytes to determine character * length. Therefore, callers satisfying none of those conditions must use * pg_encoding_mblen_or_incomplete() instead, as access to mbstr[1] cannot be * guaranteed to be within allocation bounds. * * When dealing with text that is not certainly valid in the specified * encoding, the result may exceed the actual remaining string length. * Callers that are not prepared to deal with that should use Min(remaining, * pg_encoding_mblen_or_incomplete()). For zero-terminated strings, that and * pg_encoding_mblen_bounded() are interchangeable. */ int pg_encoding_mblen(int encoding, const char *mbstr) { return (PG_VALID_ENCODING(encoding) ? pg_wchar_table[encoding].mblen( (const unsigned char *)mbstr) : pg_wchar_table[PG_SQL_ASCII].mblen( (const unsigned char *)mbstr)); } /* * Returns the byte length of a multibyte character (possibly not * zero-terminated), or INT_MAX if too few bytes remain to determine a length. */ int pg_encoding_mblen_or_incomplete(int encoding, const char *mbstr, size_t remaining) { /* * Define zero remaining as too few, even for single-byte encodings. * pg_gb18030_mblen() reads one or two bytes; single-byte encodings read * zero; others read one. */ if (remaining < 1 || (encoding == PG_GB18030 && IS_HIGHBIT_SET(*mbstr) && remaining < 2)) { return INT_MAX; } return pg_encoding_mblen(encoding, mbstr); } /* * Returns the byte length of a multibyte character; but not more than the * distance to the terminating zero byte. For input that might lack a * terminating zero, use Min(remaining, pg_encoding_mblen_or_incomplete()). */ int pg_encoding_mblen_bounded(int encoding, const char *mbstr) { return strnlen(mbstr, pg_encoding_mblen(encoding, mbstr)); } /* * Returns the display length of a multibyte character. */ int pg_encoding_dsplen(int encoding, const char *mbstr) { return (PG_VALID_ENCODING(encoding) ? pg_wchar_table[encoding].dsplen( (const unsigned char *)mbstr) : pg_wchar_table[PG_SQL_ASCII].dsplen( (const unsigned char *)mbstr)); } /* * Verify the first multibyte character of the given string. * Return its byte length if good, -1 if bad. (See comments above for * full details of the mbverifychar API.) */ int pg_encoding_verifymbchar(int encoding, const char *mbstr, int len) { return (PG_VALID_ENCODING(encoding) ? pg_wchar_table[encoding].mbverifychar( (const unsigned char *)mbstr, len) : pg_wchar_table[PG_SQL_ASCII].mbverifychar( (const unsigned char *)mbstr, len)); } /* * Verify that a string is valid for the given encoding. * Returns the number of input bytes (<= len) that form a valid string. * (See comments above for full details of the mbverifystr API.) */ int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len) { return (PG_VALID_ENCODING(encoding) ? pg_wchar_table[encoding].mbverifystr( (const unsigned char *)mbstr, len) : pg_wchar_table[PG_SQL_ASCII].mbverifystr( (const unsigned char *)mbstr, len)); } /* * fetch maximum length of a given encoding */ int pg_encoding_max_length(int encoding) { Assert(PG_VALID_ENCODING(encoding)); /* * Check for the encoding despite the assert, due to some mingw versions * otherwise issuing bogus warnings. */ return PG_VALID_ENCODING(encoding) ? pg_wchar_table[encoding].maxmblen : pg_wchar_table[PG_SQL_ASCII].maxmblen; } odyssey-1.6.0-rc1/sources/compression.c000066400000000000000000000030771526064202200200760ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include int od_compression_frontend_setup(od_client_t *client, od_config_listen_t *config, od_logger_t *logger) { kiwi_var_t *compression_var = kiwi_vars_get(&client->vars, KIWI_VAR_COMPRESSION); if (compression_var == NULL) { /* if there is no compression variable in startup packet, * skip compression initialization */ return 0; } char *client_compression_algorithms = compression_var->value; char compression_algorithm = MM_ZPQ_NO_COMPRESSION; /* if compression support is enabled, choose the compression algorithm */ if (config->compression) { compression_algorithm = machine_compression_choose_alg( client_compression_algorithms); } machine_msg_t *msg = kiwi_be_write_compression_ack(NULL, compression_algorithm); if (msg == NULL) { return -1; } int rc = od_write(&client->io, msg); if (rc == -1) { od_error(logger, "compression", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } if (compression_algorithm == MM_ZPQ_NO_COMPRESSION) { /* do not perform the compression initialization * if failed to choose any compression algorithm */ return 0; } /* initialize compression */ rc = mm_io_set_compression(client->io.io, compression_algorithm); if (rc == -1) { od_debug(logger, "compression", client, NULL, "failed to initialize compression w/ algorithm %c", compression_algorithm); return -1; } return 0; } odyssey-1.6.0-rc1/sources/config.c000066400000000000000000000654641526064202200170120ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include void od_config_init(od_config_t *config) { config->daemonize = 0; config->priority = 0; config->sequential_routing = 0; config->log_debug = 0; config->log_to_stdout = 1; config->log_config = 0; config->log_session = 1; config->log_query = 0; config->log_file = NULL; config->log_stats = 1; config->log_async = 1; config->log_queue_depth = 30000; config->log_general_stats_prom = 0; config->log_route_stats_prom = 0; config->stats_interval = 3; config->log_format = NULL; config->pid_file = NULL; config->unix_socket_dir = NULL; config->locks_dir = NULL; config->external_auth_socket_path = NULL; config->enable_online_restart_feature = 1; config->conn_drop_options.drop_enabled = 1; config->conn_drop_options.rate = 1; config->conn_drop_options.interval_ms = 1000; /* 1 sec */ config->bindwith_reuseport = 1; config->graceful_die_on_errors = 0; config->unix_socket_mode = NULL; config->virtual_transaction = 1; config->log_syslog = 0; config->log_syslog_ident = NULL; config->log_syslog_facility = NULL; config->readahead = sysconf(_SC_PAGESIZE); config->nodelay = 1; config->disable_nolinger = 0; config->keepalive = 15; config->keepalive_keep_interval = 5; config->keepalive_probes = 3; config->keepalive_usr_timeout = 0; /* use sys default */ config->cpu_affinity = NULL; // od_affinity_config_init(&config->cpu_affinity); config->workers = 1; config->resolvers = 1; config->client_max_set = 0; config->client_max = 0; config->client_max_routing = 0; config->accept_rate_limit = 0; config->server_login_retry = 1; config->cache_coroutine = 1024; config->cache_msg_gc_size = 0; config->cache_msg_gc_count = 1024; config->coroutine_stack_size = 16; config->system_coroutine_stack_size = 32; config->hba_file = NULL; config->max_sigterms_to_die = 3; config->group_checker_interval = 7000; /* 7 seconds */ od_list_init(&config->listen); config->backend_connect_timeout_ms = 30U * 1000U; /* 30 seconds */ config->cancel_timeout_ms = 1U * 1000U; /* 1 seconds */ config->cancel_queue_timeout_ms = -1; config->cancel_max_inflight = -1; config->cancel_rate_limit = 0; config->virtual_processing = 0; config->graceful_shutdown_timeout_ms = 30 * 1000; /* 30 seconds */ memset(config->availability_zone, 0, sizeof(config->availability_zone)); memset(&config->soft_oom, 0, sizeof(config->soft_oom)); config->soft_oom.check_interval_ms = 1000; config->soft_oom.drop.enabled = 0; config->soft_oom.drop.max_rate = 3; config->soft_oom.drop.signal = SIGTERM; memset(&config->query_parsing, 0, sizeof(config->query_parsing)); config->query_parsing.mem_limit_bytes = 1024 * 1024; config->query_parsing.mode = OD_CONFIG_QUERY_PARSING_MODE_MINIMAL; config->host_watcher_enabled = 0; config->smart_search_path_enquoting = 0; config->dns_ttl_ms = 30 * 1000; } void od_config_reload(od_config_t *current_config, od_config_t *new_config) { current_config->log_debug = new_config->log_debug; current_config->log_config = new_config->log_config; current_config->log_session = new_config->log_session; current_config->log_query = new_config->log_query; current_config->log_stats = new_config->log_stats; current_config->stats_interval = new_config->stats_interval; current_config->client_max_set = new_config->client_max_set; current_config->client_max = new_config->client_max; current_config->max_sigterms_to_die = new_config->max_sigterms_to_die; current_config->client_max_routing = new_config->client_max_routing; current_config->accept_rate_limit = new_config->accept_rate_limit; current_config->server_login_retry = new_config->server_login_retry; current_config->backend_connect_timeout_ms = new_config->backend_connect_timeout_ms; current_config->cancel_timeout_ms = new_config->cancel_timeout_ms; current_config->smart_search_path_enquoting = new_config->smart_search_path_enquoting; current_config->disable_nolinger = new_config->disable_nolinger; current_config->keepalive = new_config->keepalive; current_config->keepalive_keep_interval = new_config->keepalive_keep_interval; current_config->keepalive_probes = new_config->keepalive_probes; current_config->keepalive_usr_timeout = new_config->keepalive_usr_timeout; current_config->graceful_shutdown_timeout_ms = new_config->graceful_shutdown_timeout_ms; } static void od_config_listen_free(od_config_listen_t *); void od_config_free(od_config_t *config) { od_list_t *i, *n; od_list_foreach_safe (&config->listen, i, n) { od_config_listen_t *listen; listen = od_container_of(i, od_config_listen_t, link); od_config_listen_free(listen); } if (config->unix_socket_mode) { od_free(config->unix_socket_mode); } if (config->log_file) { od_free(config->log_file); } if (config->log_format) { od_free(config->log_format); } if (config->pid_file) { od_free(config->pid_file); } if (config->unix_socket_dir) { od_free(config->unix_socket_dir); } if (config->log_syslog_ident) { od_free(config->log_syslog_ident); } if (config->log_syslog_facility) { od_free(config->log_syslog_facility); } if (config->locks_dir) { od_free(config->locks_dir); if (config->hba_file) { od_free(config->hba_file); } } if (config->external_auth_socket_path) { od_free(config->external_auth_socket_path); } if (config->cpu_affinity) { od_free(config->cpu_affinity); } } od_config_listen_t *od_config_listen_add(od_config_t *config) { od_config_listen_t *listen = (od_config_listen_t *)od_malloc(sizeof(od_config_listen_t)); if (listen == NULL) { return NULL; } memset(listen, 0, sizeof(*listen)); listen->tls_opts = od_tls_opts_alloc(); if (listen->tls_opts == NULL) { od_free(listen); return NULL; } listen->port = 6432; listen->backlog = 128; listen->client_login_timeout = 15000; listen->reserved_clients = -1; /* disabled */ listen->target_session_attrs = OD_TARGET_SESSION_ATTRS_UNDEF; listen->balancing_override_set = 0; od_storage_balancing_init(&listen->balancing_override); od_list_init(&listen->link); od_list_append(&config->listen, &listen->link); return listen; } static void od_config_listen_free(od_config_listen_t *config) { if (config->host) { od_free(config->host); } if (config->tls_opts) { od_tls_opts_free(config->tls_opts); } od_storage_balancing_destroy(&config->balancing_override); for (size_t i = 0; i < config->storage_count; ++i) { od_free(config->storage_names[i]); } od_free(config->storage_names); od_free(config); } int od_config_validate(od_config_t *config, od_logger_t *logger) { if (config->workers <= 0) { od_error(logger, "config", NULL, NULL, "bad workers number"); return -1; } if (config->resolvers <= 0) { od_error(logger, "config", NULL, NULL, "bad resolvers number"); return -1; } if (config->coroutine_stack_size < 4) { od_error(logger, "config", NULL, NULL, "bad coroutine_stack_size number"); return -1; } if (config->system_coroutine_stack_size < 4) { od_error(logger, "config", NULL, NULL, "bad system_coroutine_stack_size number"); return -1; } if (config->log_format == NULL) { od_error(logger, "config", NULL, NULL, "log_format is not defined"); return -1; } /* unix_socket_mode */ if (config->unix_socket_dir) { if (config->unix_socket_mode == NULL) { od_error(logger, "config", NULL, NULL, "unix_socket_mode is not set"); return -1; } } /* listen */ if (od_list_empty(&config->listen)) { od_error(logger, "config", NULL, NULL, "no listen servers defined"); return -1; } od_list_t *i; od_list_foreach (&config->listen, i) { od_config_listen_t *listen; listen = od_container_of(i, od_config_listen_t, link); if (listen->host == NULL) { if (config->unix_socket_dir == NULL) { od_error( logger, "config", NULL, NULL, "listen host is not set and no unix_socket_dir is specified"); return -1; } } /* tls options */ if (listen->tls_opts->tls) { if (strcmp(listen->tls_opts->tls, "disable") == 0) { listen->tls_opts->tls_mode = OD_CONFIG_TLS_DISABLE; } else if (strcmp(listen->tls_opts->tls, "allow") == 0) { listen->tls_opts->tls_mode = OD_CONFIG_TLS_ALLOW; } else if (strcmp(listen->tls_opts->tls, "prefer") == 0) { listen->tls_opts->tls_mode = OD_CONFIG_TLS_PREFER; } else if (strcmp(listen->tls_opts->tls, "require") == 0) { listen->tls_opts->tls_mode = OD_CONFIG_TLS_REQUIRE; } else if (strcmp(listen->tls_opts->tls, "verify_ca") == 0) { listen->tls_opts->tls_mode = OD_CONFIG_TLS_VERIFY_CA; } else if (strcmp(listen->tls_opts->tls, "verify_full") == 0) { listen->tls_opts->tls_mode = OD_CONFIG_TLS_VERIFY_FULL; } else { od_error(logger, "config", NULL, NULL, "unknown tls_opts->tls mode"); return -1; } } /* * Report a broken certificate up front instead of failing * every connection later. With "allow" and "prefer" the * listener still serves plaintext clients, so only a mandatory * tls mode is a fatal config error. */ char tls_error[256]; if (listen->tls_opts->tls_mode != OD_CONFIG_TLS_DISABLE && od_tls_frontend_validate(listen, tls_error, sizeof(tls_error)) != OK_RESPONSE) { int tls_optional = listen->tls_opts->tls_mode == OD_CONFIG_TLS_ALLOW || listen->tls_opts->tls_mode == OD_CONFIG_TLS_PREFER; od_error( logger, "config", NULL, NULL, "failed to load tls certificate for %s:%d%s: %s", od_config_listen_host_name(listen), listen->port, tls_optional ? ", serving plaintext only" : "", tls_error); if (!tls_optional) { return -1; } } } if (config->enable_online_restart_feature && !config->bindwith_reuseport) { od_error( logger, "config", NULL, NULL, "online restart feature works only with SO_REUSEPORT. Disable " "online restart or/and enable bindwith_reuseport"); return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline char *od_config_yes_no(int value) { return value ? "yes" : "no"; } /* * Description of the global configuration for SHOW CONFIG. * * The default column is not stored here on purpose: od_config_field_default() * initializes a throwaway od_config_t through od_config_init() and reads the * same offset, so the reported defaults cannot drift away from the real ones. * * The reloadable flag has no such source and must be kept in sync with * od_config_reload() by hand. */ #define OD_CONFIG_FIELD_NOT_SET "(not set)" static const od_config_field_t od_config_fields[] = { { "daemonize", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, daemonize), 0 }, { "priority", OD_CONFIG_FIELD_INT, offsetof(od_config_t, priority), 0 }, { "sequential_routing", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, sequential_routing), 0 }, { "log_to_stdout", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_to_stdout), 0 }, { "log_debug", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_debug), 1 }, { "log_config", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_config), 1 }, { "log_session", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_session), 1 }, { "log_query", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_query), 1 }, { "log_queue_depth", OD_CONFIG_FIELD_INT, offsetof(od_config_t, log_queue_depth), 0 }, { "log_async", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_async), 0 }, { "log_file", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, log_file), 0 }, { "log_format", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, log_format), 0 }, { "log_stats", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_stats), 1 }, { "log_general_stats_prom", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_general_stats_prom), 0 }, { "log_route_stats_prom", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_route_stats_prom), 0 }, { "log_syslog", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, log_syslog), 0 }, { "log_syslog_ident", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, log_syslog_ident), 0 }, { "log_syslog_facility", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, log_syslog_facility), 0 }, { "stats_interval", OD_CONFIG_FIELD_INT, offsetof(od_config_t, stats_interval), 1 }, { "pid_file", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, pid_file), 0 }, { "unix_socket_dir", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, unix_socket_dir), 0 }, { "unix_socket_mode", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, unix_socket_mode), 0 }, { "locks_dir", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, locks_dir), 0 }, { "external_auth_socket_path", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, external_auth_socket_path), 0 }, { "graceful_die_on_errors", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, graceful_die_on_errors), 0 }, { "graceful_shutdown_timeout_ms", OD_CONFIG_FIELD_INT, offsetof(od_config_t, graceful_shutdown_timeout_ms), 1 }, { "enable_online_restart", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, enable_online_restart_feature), 0 }, { "bindwith_reuseport", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, bindwith_reuseport), 0 }, { "readahead", OD_CONFIG_FIELD_INT, offsetof(od_config_t, readahead), 0 }, { "nodelay", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, nodelay), 0 }, { "disable_nolinger", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, disable_nolinger), 1 }, { "keepalive", OD_CONFIG_FIELD_INT, offsetof(od_config_t, keepalive), 1 }, { "keepalive_keep_interval", OD_CONFIG_FIELD_INT, offsetof(od_config_t, keepalive_keep_interval), 1 }, { "keepalive_probes", OD_CONFIG_FIELD_INT, offsetof(od_config_t, keepalive_probes), 1 }, { "keepalive_usr_timeout", OD_CONFIG_FIELD_INT, offsetof(od_config_t, keepalive_usr_timeout), 1 }, { "workers", OD_CONFIG_FIELD_INT, offsetof(od_config_t, workers), 0 }, { "resolvers", OD_CONFIG_FIELD_INT, offsetof(od_config_t, resolvers), 0 }, { "client_max", OD_CONFIG_FIELD_INT, offsetof(od_config_t, client_max), 1 }, { "client_max_routing", OD_CONFIG_FIELD_INT, offsetof(od_config_t, client_max_routing), 1 }, { "accept_rate_limit", OD_CONFIG_FIELD_INT, offsetof(od_config_t, accept_rate_limit), 1 }, { "server_login_retry", OD_CONFIG_FIELD_INT, offsetof(od_config_t, server_login_retry), 1 }, { "cache_coroutine", OD_CONFIG_FIELD_INT, offsetof(od_config_t, cache_coroutine), 0 }, { "cache_msg_gc_size", OD_CONFIG_FIELD_INT, offsetof(od_config_t, cache_msg_gc_size), 0 }, { "cache_msg_gc_count", OD_CONFIG_FIELD_INT, offsetof(od_config_t, cache_msg_gc_count), 0 }, { "coroutine_stack_size", OD_CONFIG_FIELD_INT, offsetof(od_config_t, coroutine_stack_size), 0 }, { "system_coroutine_stack_size", OD_CONFIG_FIELD_INT, offsetof(od_config_t, system_coroutine_stack_size), 0 }, { "dns_cache_ttl", OD_CONFIG_FIELD_INT, offsetof(od_config_t, dns_ttl_ms), 0 }, { "hba_file", OD_CONFIG_FIELD_STRING, offsetof(od_config_t, hba_file), 0 }, { "group_checker_interval", OD_CONFIG_FIELD_INT, offsetof(od_config_t, group_checker_interval), 0 }, { "backend_connect_timeout_ms", OD_CONFIG_FIELD_INT, offsetof(od_config_t, backend_connect_timeout_ms), 1 }, { "cancel_timeout_ms", OD_CONFIG_FIELD_INT, offsetof(od_config_t, cancel_timeout_ms), 1 }, { "cancel_queue_timeout_ms", OD_CONFIG_FIELD_INT, offsetof(od_config_t, cancel_queue_timeout_ms), 0 }, { "cancel_max_inflight", OD_CONFIG_FIELD_INT, offsetof(od_config_t, cancel_max_inflight), 0 }, { "virtual_processing", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, virtual_processing), 0 }, { "virtual_transaction", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, virtual_transaction), 0 }, { "availability_zone", OD_CONFIG_FIELD_STRING_INLINE, offsetof(od_config_t, availability_zone), 0 }, { "max_sigterms_to_die", OD_CONFIG_FIELD_INT, offsetof(od_config_t, max_sigterms_to_die), 1 }, { "enable_host_watcher", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, host_watcher_enabled), 0 }, { "smart_search_path_enquoting", OD_CONFIG_FIELD_BOOL, offsetof(od_config_t, smart_search_path_enquoting), 1 }, }; size_t od_config_fields_count(void) { return sizeof(od_config_fields) / sizeof(od_config_fields[0]); } const od_config_field_t *od_config_field_at(size_t index) { if (index >= od_config_fields_count()) { return NULL; } return &od_config_fields[index]; } void od_config_field_value(od_config_t *config, const od_config_field_t *field, char *buf, size_t size) { char *at = (char *)config + field->offset; switch (field->type) { case OD_CONFIG_FIELD_INT: od_snprintf(buf, size, "%d", *(int *)at); break; case OD_CONFIG_FIELD_BOOL: od_snprintf(buf, size, "%s", *(int *)at ? "yes" : "no"); break; case OD_CONFIG_FIELD_STRING: { char *value = *(char **)at; od_snprintf(buf, size, "%s", value ? value : OD_CONFIG_FIELD_NOT_SET); break; } case OD_CONFIG_FIELD_STRING_INLINE: od_snprintf(buf, size, "%s", at[0] ? at : OD_CONFIG_FIELD_NOT_SET); break; default: od_snprintf(buf, size, "%s", OD_CONFIG_FIELD_NOT_SET); break; } } /* * Defaults are shared by every caller and never change, so they are built * once per process. od_config_t is too large to keep on a coroutine stack, * and od_config_init() allocates nothing, so this instance needs no * matching od_config_free(). */ static od_config_t od_config_defaults; static pthread_once_t od_config_defaults_ctrl = PTHREAD_ONCE_INIT; static void od_config_defaults_build(void) { od_config_init(&od_config_defaults); } void od_config_field_default(const od_config_field_t *field, char *buf, size_t size) { pthread_once(&od_config_defaults_ctrl, od_config_defaults_build); od_config_field_value(&od_config_defaults, field, buf, size); } void od_config_print(od_config_t *config, od_logger_t *logger) { od_log(logger, "config", NULL, NULL, "daemonize %s", od_config_yes_no(config->daemonize)); od_log(logger, "config", NULL, NULL, "priority %d", config->priority); od_log(logger, "config", NULL, NULL, "sequential_routing %s", od_config_yes_no(config->sequential_routing)); if (config->pid_file) { od_log(logger, "config", NULL, NULL, "pid_file %s", config->pid_file); } if (config->unix_socket_dir) { od_log(logger, "config", NULL, NULL, "unix_socket_dir %s", config->unix_socket_dir); od_log(logger, "config", NULL, NULL, "unix_socket_mode %s", config->unix_socket_mode); } if (config->log_format) { od_log(logger, "config", NULL, NULL, "log_format %s", config->log_format); } if (config->log_file) { od_log(logger, "config", NULL, NULL, "log_file %s", config->log_file); } od_log(logger, "config", NULL, NULL, "log_to_stdout %s", od_config_yes_no(config->log_to_stdout)); od_log(logger, "config", NULL, NULL, "log_syslog %s", od_config_yes_no(config->log_syslog)); if (config->log_syslog_ident) { od_log(logger, "config", NULL, NULL, "log_syslog_ident %s", config->log_syslog_ident); } if (config->log_syslog_facility) { od_log(logger, "config", NULL, NULL, "log_syslog_facility %s", config->log_syslog_facility); } od_log(logger, "config", NULL, NULL, "log_debug %s", od_config_yes_no(config->log_debug)); od_log(logger, "config", NULL, NULL, "log_config %s", od_config_yes_no(config->log_config)); od_log(logger, "config", NULL, NULL, "log_session %s", od_config_yes_no(config->log_session)); od_log(logger, "config", NULL, NULL, "log_query %s", od_config_yes_no(config->log_query)); od_log(logger, "config", NULL, NULL, "log_stats %s", od_config_yes_no(config->log_stats)); od_log(logger, "config", NULL, NULL, "log_general_stats_prom %s", od_config_yes_no(config->log_general_stats_prom)); od_log(logger, "config", NULL, NULL, "log_route_stats_prom %s", od_config_yes_no(config->log_route_stats_prom)); od_log(logger, "config", NULL, NULL, "log_queue_depth %d", config->log_queue_depth); od_log(logger, "config", NULL, NULL, "stats_interval %d", config->stats_interval); od_log(logger, "config", NULL, NULL, "readahead %d", config->readahead); od_log(logger, "config", NULL, NULL, "nodelay %s", od_config_yes_no(config->nodelay)); od_log(logger, "config", NULL, NULL, "disable_nolinger %s", od_config_yes_no(config->disable_nolinger)); od_log(logger, "config", NULL, NULL, "keepalive %d", config->keepalive); od_log(logger, "config", NULL, NULL, "keepalive_keep_interval %d", config->keepalive_keep_interval); od_log(logger, "config", NULL, NULL, "keepalive_probes %d", config->keepalive_probes); od_log(logger, "config", NULL, NULL, "keepalive_usr_timeout %d", config->keepalive_usr_timeout); if (config->client_max_set) { od_log(logger, "config", NULL, NULL, "client_max %d", config->client_max); } od_log(logger, "config", NULL, NULL, "client_max_routing %d", config->client_max_routing); od_log(logger, "config", NULL, NULL, "server_login_retry %d", config->server_login_retry); od_log(logger, "config", NULL, NULL, "cache_msg_gc_size %d", config->cache_msg_gc_size); od_log(logger, "config", NULL, NULL, "cache_msg_gc_count %d", config->cache_msg_gc_count); od_log(logger, "config", NULL, NULL, "cache_coroutine %d", config->cache_coroutine); od_log(logger, "config", NULL, NULL, "coroutine_stack_size %d", config->coroutine_stack_size); od_log(logger, "config", NULL, NULL, "system_coroutine_stack_size %d", config->system_coroutine_stack_size); od_log(logger, "config", NULL, NULL, "workers %d", config->workers); od_log(logger, "config", NULL, NULL, "resolvers %d", config->resolvers); od_log(logger, "config", NULL, NULL, "backend_connect_timeout_ms %u", config->backend_connect_timeout_ms); od_log(logger, "config", NULL, NULL, "cancel_timeout_ms %u", config->cancel_timeout_ms); od_log(logger, "config", NULL, NULL, "cancel_queue_timeout_ms %u", config->cancel_queue_timeout_ms); od_log(logger, "config", NULL, NULL, "cancel_max_inflight %d", config->cancel_max_inflight); od_log(logger, "config", NULL, NULL, "cancel_rate_limit %d", config->cancel_rate_limit); od_log(logger, "config", NULL, NULL, "dns_ttl_ms %d", config->dns_ttl_ms); od_log(logger, "config", NULL, NULL, "group_checker_interval %d", config->group_checker_interval); od_log(logger, "config", NULL, NULL, "max_sigterms_to_die %d", config->max_sigterms_to_die); od_log(logger, "config", NULL, NULL, "virtual_processing %s", od_config_yes_no(config->virtual_processing)); od_log(logger, "config", NULL, NULL, "virtual_transaction %s", od_config_yes_no(config->virtual_transaction)); od_log(logger, "config", NULL, NULL, "smart_search_path_enquoting %s", od_config_yes_no(config->smart_search_path_enquoting)); if (config->availability_zone[0]) { od_log(logger, "config", NULL, NULL, "availability_zone %s", config->availability_zone); } od_log(logger, "config", NULL, NULL, "enable_host_watcher: %d", config->host_watcher_enabled); od_log(logger, "config", NULL, NULL, "online restart enabled: %d", config->enable_online_restart_feature); od_log(logger, "config", NULL, NULL, "graceful die enabled: %d", config->graceful_die_on_errors); od_log(logger, "config", NULL, NULL, "bindwith_reuseport: %d", config->bindwith_reuseport); if (config->hba_file) { od_log(logger, "config", NULL, NULL, "hba_file %s", config->hba_file); } if (config->conn_drop_options.drop_enabled) { od_log(logger, "config", NULL, NULL, "conn_drop_options: enabled, rate %d, interval_ms %d", config->conn_drop_options.rate, config->conn_drop_options.interval_ms); } if (config->soft_oom.enabled) { od_log(logger, "config", NULL, NULL, "soft_oom: check '%s' every %dms with limit %" PRIu64 " bytes", config->soft_oom.process, config->soft_oom.check_interval_ms, config->soft_oom.limit_bytes); if (config->soft_oom.drop.enabled) { od_log(logger, "config", NULL, NULL, "soft_oom drop: signal %d, max_rate %d", config->soft_oom.drop.signal, config->soft_oom.drop.max_rate); } } else { od_log(logger, "config", NULL, NULL, "soft_oom: DISABLED"); } od_list_t *i; od_list_foreach (&config->listen, i) { od_config_listen_t *listen; listen = od_container_of(i, od_config_listen_t, link); od_log(logger, "config", NULL, NULL, "listen"); od_log(logger, "config", NULL, NULL, " host %s", listen->host ? listen->host : ""); od_log(logger, "config", NULL, NULL, " port %d", listen->port); od_log(logger, "config", NULL, NULL, " backlog %d", listen->backlog); od_log(logger, "config", NULL, NULL, " client_login_timeout %d", listen->client_login_timeout); od_log(logger, "config", NULL, NULL, " reserved_clients %d", listen->reserved_clients); od_log(logger, "config", NULL, NULL, " target_session_attrs %s", od_target_session_attrs_to_str( listen->target_session_attrs)); if (listen->balancing_override_set) { od_log(logger, "config", NULL, NULL, " balancing override: method %s, az_aware %d", od_balancing_method_to_str( listen->balancing_override.method.type), listen->balancing_override.method.az_aware); } if (listen->catchup_timeout) { od_log(logger, "config", NULL, NULL, " catchup_timeout %d", listen->catchup_timeout); } if (listen->tls_opts->tls) { od_log(logger, "config", NULL, NULL, " tls %s", listen->tls_opts->tls); } if (listen->tls_opts->tls_ca_file) { od_log(logger, "config", NULL, NULL, " tls_ca_file %s", listen->tls_opts->tls_ca_file); } if (listen->tls_opts->tls_key_file) { od_log(logger, "config", NULL, NULL, " tls_key_file %s", listen->tls_opts->tls_key_file); } if (listen->tls_opts->tls_cert_file) { od_log(logger, "config", NULL, NULL, " tls_cert_file %s", listen->tls_opts->tls_cert_file); } if (listen->tls_opts->tls_protocols) { od_log(logger, "config", NULL, NULL, " tls_protocols %s", listen->tls_opts->tls_protocols); } for (size_t i = 0; i < listen->storage_count; ++i) { od_log(logger, "config", NULL, NULL, " storage %s", listen->storage_names[i]); } } } odyssey-1.6.0-rc1/sources/console.c000066400000000000000000002124021526064202200171710ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline int od_console_show_stats_add(machine_msg_t *stream, char *database, int database_len, od_stat_t *total, od_stat_t *avg) { od_assert(stream); int offset; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } int rc; rc = kiwi_be_write_data_row_add(stream, offset, database, database_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } char data[64]; int data_len; uint64_t stats[] = { total->count_tx, /* total_xact_count */ total->count_query, /* total_query_count */ total->count_wait, /* total_server_assignment_count */ total->recv_client, /* total_received */ total->recv_server, /* total_sent */ total->tx_time, /* total_xact_time */ total->query_time, /* total_query_time */ total->wait_time_us, /* total_wait_time */ avg->count_tx, /* avg_xact_count */ avg->count_query, /* avg_query_count */ avg->count_wait, /* avg_server_assignment_count */ avg->recv_client, /* avg_recv */ avg->recv_server, /* avg_sent */ avg->tx_time, /* avg_xact_time */ avg->query_time, /* avg_query_time */ avg->wait_time_us, /* avg_wait_time */ total->count_parse, /* count of backend parse msgs */ total->count_parse_reuse, /* count of backend parse msgs reuse */ total->count_cancel /* total_cancel_count */ }; for (size_t i = 0; i < sizeof(stats) / sizeof(stats[0]); i++) { data_len = od_snprintf(data, sizeof(data), "%" PRIu64, stats[i]); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } } return 0; } static inline od_retcode_t od_console_show_frontend_stats_err_add(machine_msg_t *stream, od_route_pool_t *route_pool) { od_assert(stream); for (size_t i = 0; i < OD_FRONTEND_STATUS_ERRORS_TYPES_COUNT; ++i) { int offset; int rc; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } size_t total_count = od_err_logger_get_aggr_errors_count( route_pool->err_logger, od_frontend_status_errs[i]); char *err_type = od_frontend_status_to_str(od_frontend_status_errs[i]); rc = kiwi_be_write_data_row_add(stream, offset, err_type, strlen(err_type)); if (rc != OK_RESPONSE) { return rc; } char data[64]; int data_len; /* error_type */ data_len = od_snprintf(data, sizeof(data), "%zu", total_count); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } } return OK_RESPONSE; } static inline int od_console_show_router_stats_err_add(machine_msg_t *stream, od_error_logger_t *err_logger) { od_assert(stream); for (size_t i = 0; i < OD_ROUTER_STATUS_ERRORS_TYPES_COUNT; ++i) { int offset; int rc; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } char *err_type = od_router_status_to_str(od_router_status_errs[i]); rc = kiwi_be_write_data_row_add(stream, offset, err_type, strlen(err_type)); if (rc != OK_RESPONSE) { return rc; } /* error_type */ char data[64]; int data_len; data_len = od_snprintf(data, sizeof(data), "%zu", od_err_logger_get_aggr_errors_count( err_logger, od_router_status_errs[i])); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } } return OK_RESPONSE; } static int od_console_show_stats_cb(char *database, int database_len, od_stat_t *total, od_stat_t *avg, void **argv) { machine_msg_t *stream = argv[0]; return od_console_show_stats_add(stream, database, database_len, total, avg); } static int od_console_show_err_frontend_stats_cb(od_route_pool_t *pool, void **argv) { machine_msg_t *stream = argv[0]; return od_console_show_frontend_stats_err_add(stream, pool); } static int od_console_show_err_router_stats_cb(od_error_logger_t *l, void **argv) { machine_msg_t *stream = argv[0]; return od_console_show_router_stats_err_add(stream, l); } static inline int od_console_show_help(machine_msg_t *stream) { od_assert(stream); char *message = "\n" "Console usage\n" "\tSHOW STATS|HELP|POOLS|POOLS_EXTENDED|DATABASES|SERVER_PREP_STMTS|SERVERS|CLIENTS|HOST_UTILIZATION\n" "\tSHOW LISTS|INSTANCE|ERRORS|ERRORS_PER_ROUTE|VERSION|VERSION_EXTENDED|LISTEN|STORAGES\n" "\tSHOW CONFIG|RULES|FDS|IS_PAUSED|GLOBAL_PREPARED_STATEMENTS\n" "\tKILL_CLIENT \n" "\tRELOAD\n" "\tGC\n" "\tPAUSE\n" "\tRESUME\n" "\tSET key=arg\n" "\tCREATE \n" "\tDROP SERVERS|MODULE |"; stream = kiwi_be_write_notice_console_usage(stream, message); int rc = kiwi_be_write_complete(stream, "SHOW", 5); return rc; } static inline int od_console_show_stats(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; od_cron_t *cron = client->global->cron; if (kiwi_be_write_row_descriptionf( stream, "slllllllllllllllllll", "database", "total_xact_count", "total_query_count", "total_server_assignment_count", "total_received", "total_sent", "total_xact_time", "total_query_time", "total_wait_time", "avg_xact_count", "avg_query_count", "avg_server_assignment_count", "avg_recv", "avg_sent", "avg_xact_time", "avg_query_time", "avg_wait_time", "total_parse_count", "total_parse_count_reuse", "total_cancel_count") == NULL) { return NOT_OK_RESPONSE; } void *argv[] = { stream }; od_route_pool_stat_database(&router->route_pool, od_console_show_stats_cb, cron->stat_time_us, argv); int rc = kiwi_be_write_complete(stream, "SHOW", 5); if (rc == NOT_OK_RESPONSE) { return rc; } return rc; } static inline od_retcode_t od_console_show_errors(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; void *argv[] = { stream }; if (kiwi_be_write_row_descriptionf(stream, "sl", "error_type", "count") == NULL) { return NOT_OK_RESPONSE; } int rc; rc = od_route_pool_stat_err_router( router, od_console_show_err_router_stats_cb, argv); if (rc != OK_RESPONSE) { return rc; } rc = od_route_pool_stat_err_frontend( &router->route_pool, od_console_show_err_frontend_stats_cb, argv); if (rc != OK_RESPONSE) { return rc; } rc = kiwi_be_write_complete(stream, "SHOW", 5); return rc; } static inline int od_console_show_errors_per_route_cb(od_route_t *route, void **argv) { machine_msg_t *stream = argv[0]; od_assert(stream); if (!route || !route->extra_logging_enabled) { return OK_RESPONSE; } for (size_t i = 0; i < OD_FRONTEND_STATUS_ERRORS_TYPES_COUNT; ++i) { int offset; int rc; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { /* message was not successfully allocated */ return NOT_OK_RESPONSE; } size_t total_count = od_err_logger_get_aggr_errors_count( route->err_logger, od_frontend_status_errs[i]); char *err_type = od_frontend_status_to_str(od_frontend_status_errs[i]); rc = kiwi_be_write_data_row_add(stream, offset, err_type, strlen(err_type)); if (rc != OK_RESPONSE) { return rc; } /* route user */ rc = kiwi_be_write_data_row_add(stream, offset, route->rule->user_name, strlen(route->rule->user_name)); if (rc != OK_RESPONSE) { return rc; } /* route database */ rc = kiwi_be_write_data_row_add(stream, offset, route->rule->db_name, strlen(route->rule->db_name)); if (rc != OK_RESPONSE) { return rc; } /* error_type */ char data[64]; int data_len; data_len = od_snprintf(data, sizeof(data), "%zu", total_count); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } } for (size_t i = 0; i < OD_ROUTER_ROUTE_STATUS_ERRORS_TYPES_COUNT; ++i) { int offset; int rc; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } size_t total_count = od_err_logger_get_aggr_errors_count( route->err_logger, od_router_route_status_errs[i]); char *err_type = od_router_status_to_str(od_router_route_status_errs[i]); rc = kiwi_be_write_data_row_add(stream, offset, err_type, strlen(err_type)); if (rc != OK_RESPONSE) { return rc; } /* route user */ rc = kiwi_be_write_data_row_add(stream, offset, route->rule->user_name, strlen(route->rule->user_name)); if (rc != OK_RESPONSE) { return rc; } /* route database */ rc = kiwi_be_write_data_row_add(stream, offset, route->rule->db_name, strlen(route->rule->db_name)); if (rc != OK_RESPONSE) { return rc; } /* error_type */ char data[64]; int data_len; data_len = od_snprintf(data, sizeof(data), "%zu", total_count); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } } return OK_RESPONSE; } static inline od_retcode_t od_console_show_errors_per_route(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; void *argv[] = { stream }; if (kiwi_be_write_row_descriptionf(stream, "sssl", "error_type", "user", "database", "count") == NULL) { return NOT_OK_RESPONSE; } od_router_foreach(router, od_console_show_errors_per_route_cb, argv); od_retcode_t rc = kiwi_be_write_complete(stream, "SHOW", 5); return rc; } static inline int od_console_show_version(machine_msg_t *stream) { od_assert(stream); if (kiwi_be_write_row_descriptionf(stream, "s", "version") == NULL) { return NOT_OK_RESPONSE; } int offset; if (kiwi_be_write_data_row(stream, &offset) == NULL) { return NOT_OK_RESPONSE; } char data[128]; int data_len; /* current version and build */ data_len = od_format_version_string(data, sizeof(data)); int rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } rc = kiwi_be_write_complete(stream, "SHOW", 5); return rc; } static inline int od_console_show_version_extended(machine_msg_t *stream) { od_assert(stream); if (kiwi_be_write_row_descriptionf( stream, "sssss", "version", "build_type", "compiler", "compiler_version", "arch") == NULL) { return NOT_OK_RESPONSE; } int offset; if (kiwi_be_write_data_row(stream, &offset) == NULL) { return NOT_OK_RESPONSE; } int rc; /* version (with git hash if available) */ #ifdef ODYSSEY_VERSION_GIT char version_str[64]; int version_len = od_snprintf(version_str, sizeof(version_str), "%s-%s", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT); rc = kiwi_be_write_data_row_add(stream, offset, version_str, version_len); #else rc = kiwi_be_write_data_row_add(stream, offset, ODYSSEY_VERSION_NUMBER, strlen(ODYSSEY_VERSION_NUMBER)); #endif if (rc != OK_RESPONSE) { return rc; } /* build_type */ rc = kiwi_be_write_data_row_add(stream, offset, ODYSSEY_BUILD_TYPE, strlen(ODYSSEY_BUILD_TYPE)); if (rc != OK_RESPONSE) { return rc; } /* compiler */ rc = kiwi_be_write_data_row_add(stream, offset, ODYSSEY_COMPILER_NAME, strlen(ODYSSEY_COMPILER_NAME)); if (rc != OK_RESPONSE) { return rc; } /* compiler_version */ rc = kiwi_be_write_data_row_add(stream, offset, ODYSSEY_COMPILER_VERSION, strlen(ODYSSEY_COMPILER_VERSION)); if (rc != OK_RESPONSE) { return rc; } /* arch */ rc = kiwi_be_write_data_row_add(stream, offset, ODYSSEY_BUILD_ARCH, strlen(ODYSSEY_BUILD_ARCH)); if (rc != OK_RESPONSE) { return rc; } rc = kiwi_be_write_complete(stream, "SHOW", 5); return rc; } static inline od_retcode_t od_console_show_quantiles(machine_msg_t *stream, int offset, const int quantiles_count, const double *quantiles, td_histogram_t *transactions_hgram, td_histogram_t *queries_hgram) { char data[64]; int data_len; int rc = OK_RESPONSE; for (int i = 0; i < quantiles_count; i++) { double q = quantiles[i]; /* query quantile */ double query_quantile = td_value_at(queries_hgram, q); double transaction_quantile = td_value_at(transactions_hgram, q); if (isnan(query_quantile)) { query_quantile = 0; } if (isnan(transaction_quantile)) { transaction_quantile = 0; } data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)query_quantile); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return rc; } /* transaction quantile */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)transaction_quantile); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return rc; } } return rc; } static inline int od_console_show_pools_add_cb(od_route_t *route, void **argv) { int offset; machine_msg_t *stream = argv[0]; bool *extended = argv[1]; double *quantiles = argv[2]; int *quantiles_count = argv[3]; td_histogram_t *common_transactions_hgram = argv[4]; td_histogram_t *common_queries_hgram = argv[5]; machine_msg_t *msg; td_histogram_t *transactions_hgram = NULL; td_histogram_t *queries_hgram = NULL; td_histogram_t *freeze_hgram = NULL; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } od_route_lock(route); int rc; rc = kiwi_be_write_data_row_add(stream, offset, route->id.database, route->id.database_len - 1); if (rc == NOT_OK_RESPONSE) { goto error; } rc = kiwi_be_write_data_row_add(stream, offset, route->id.user, route->id.user_len - 1); if (rc == NOT_OK_RESPONSE) { goto error; } char data[64]; int data_len; /* cl_active */ data_len = od_snprintf(data, sizeof(data), "%d", route->client_pool.count_active); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* cl_waiting */ data_len = od_snprintf(data, sizeof(data), "%d", route->client_pool.count_pending); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* sv_active */ data_len = od_snprintf(data, sizeof(data), "%d", od_route_server_pool_count_active_locked( route, 1 /* only for this route's db.user */)); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* sv_idle */ data_len = od_snprintf(data, sizeof(data), "%d", od_route_server_pool_count_idle_locked( route, 1 /* only for this route's db.user */)); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* sv_used */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* sv_tested */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* sv_login */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* reported as whole seconds plus the sub-second remainder */ uint64_t maxwait_us = od_client_pool_max_queue_time_us( &route->client_pool, machine_time_us()); /* maxwait */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, maxwait_us / 1000000); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* maxwait_us */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, maxwait_us % 1000000); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* pool_mode */ rc = NOT_OK_RESPONSE; switch (route->rule->pool->pool_type) { case OD_RULE_POOL_SESSION: rc = kiwi_be_write_data_row_add(stream, offset, "session", 7); break; case OD_RULE_POOL_TRANSACTION: rc = kiwi_be_write_data_row_add(stream, offset, "transaction", 11); break; case OD_RULE_POOL_STATEMENT: rc = kiwi_be_write_data_row_add(stream, offset, "statement", 9); break; default: break; } if (rc == NOT_OK_RESPONSE) { goto error; } if (*extended) { /* bytes received */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, route->stats.recv_client); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* bytes sent */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, route->stats.recv_server); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* tcp conn rate */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, route->tcp_connections); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* cl_queue */ data_len = od_snprintf(data, sizeof(data), "%d", route->client_pool.count_queue); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } transactions_hgram = td_new(QUANTILES_COMPRESSION); queries_hgram = td_new(QUANTILES_COMPRESSION); freeze_hgram = td_new(QUANTILES_COMPRESSION); if (route->stats.enable_quantiles) { for (size_t i = 0; i < QUANTILES_WINDOW; ++i) { td_copy(freeze_hgram, route->stats.transaction_hgram[i]); td_merge(transactions_hgram, freeze_hgram); td_copy(freeze_hgram, route->stats.query_hgram[i]); td_merge(queries_hgram, freeze_hgram); } td_merge(common_transactions_hgram, transactions_hgram); td_merge(common_queries_hgram, queries_hgram); } rc = od_console_show_quantiles(stream, offset, *quantiles_count, quantiles, transactions_hgram, queries_hgram); if (rc == NOT_OK_RESPONSE) { goto error; } } td_safe_free(transactions_hgram); td_safe_free(queries_hgram); td_safe_free(freeze_hgram); od_route_unlock(route); return 0; error: td_safe_free(transactions_hgram); td_safe_free(queries_hgram); td_safe_free(freeze_hgram); od_route_unlock(route); return NOT_OK_RESPONSE; } static inline int od_console_show_databases_add_cb(od_route_t *route, void **argv) { int offset; machine_msg_t *stream = argv[0]; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } od_route_lock(route); int rc; rc = kiwi_be_write_data_row_add(stream, offset, route->id.database, route->id.database_len - 1); if (rc == NOT_OK_RESPONSE) { goto error; } od_rule_t *rule = route->rule; od_rule_storage_t *storage = rule->storage; /* host */ char *host = storage->host; if (!host) { host = ""; } rc = kiwi_be_write_data_row_add(stream, offset, host, strlen(host)); if (rc == NOT_OK_RESPONSE) { goto error; } char data[64]; int data_len; /* port */ data_len = od_snprintf(data, sizeof(data), "%d", storage->port); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* database */ rc = kiwi_be_write_data_row_add(stream, offset, rule->db_name, rule->db_name_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* force_user */ rc = kiwi_be_write_data_row_add(stream, offset, rule->user_name, rule->user_name_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* pool size */ data_len = od_snprintf(data, sizeof(data), "%d", rule->pool->size); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* reserve_pool */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* pool mode */ rc = NOT_OK_RESPONSE; if (rule->pool->pool_type == OD_RULE_POOL_SESSION) { rc = kiwi_be_write_data_row_add(stream, offset, "session", 7); } if (rule->pool->pool_type == OD_RULE_POOL_TRANSACTION) { rc = kiwi_be_write_data_row_add(stream, offset, "transaction", 11); } if (rc == NOT_OK_RESPONSE) { goto error; } /* max_connections */ data_len = od_snprintf(data, sizeof(data), "%d", rule->client_max); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* current_connections */ data_len = od_snprintf(data, sizeof(data), "%d", route->client_pool.count_active + route->client_pool.count_pending + route->client_pool.count_queue); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* paused */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* disabled */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, (uint64_t)0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } od_route_unlock(route); return 0; error: od_route_unlock(route); return NOT_OK_RESPONSE; } static inline int od_console_show_databases(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf( stream, "sslssllsllll", "name", "host", "port", "database", "force_user", "pool_size", "reserve_pool", "pool_mode", "max_connections", "current_connections", "paused", "disabled"); if (msg == NULL) { return NOT_OK_RESPONSE; } void *argv[] = { stream }; int rc; rc = od_router_foreach(router, od_console_show_databases_add_cb, argv); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_pools(od_client_t *client, machine_msg_t *stream, bool extended) { od_assert(stream); int rc; od_router_t *router = client->global->router; od_route_t *route = client->route; double *quantiles = route->rule->quantiles; int quantiles_count = route->rule->quantiles_count; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "ssllllllllls", "database", "user", "cl_active", "cl_waiting", "sv_active", "sv_idle", "sv_used", "sv_tested", "sv_login", "maxwait", "maxwait_us", "pool_mode"); if (msg == NULL) { return NOT_OK_RESPONSE; } if (extended) { char *bytes_rcv = "bytes_received"; rc = kiwi_be_write_row_description_add(msg, 0, bytes_rcv, strlen(bytes_rcv), 0, 0, 23 /* INT4OID */, 4, 0, 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } char *bytes_sent = "bytes_sent"; rc = kiwi_be_write_row_description_add(msg, 0, bytes_sent, strlen(bytes_sent), 0, 0, 23 /* INT4OID */, 4, 0, 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } char *tcp_conn_rate = "tcp_conn_count"; rc = kiwi_be_write_row_description_add(msg, 0, tcp_conn_rate, strlen(tcp_conn_rate), 0, 0, 23 /* INT4OID */, 4, 0, 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } char *cl_queue = "cl_queue"; rc = kiwi_be_write_row_description_add(msg, 0, cl_queue, strlen(cl_queue), 0, 0, 23 /* INT4OID */, 4, 0, 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } for (int i = 0; i < quantiles_count; i++) { char caption[KIWI_MAX_VAR_SIZE]; int caption_len; caption_len = od_snprintf(caption, sizeof(caption), "query_%.6g", quantiles[i]); rc = kiwi_be_write_row_description_add( msg, 0, caption, caption_len, 0, 0, 23 /* INT4OID */, 4, 0, 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } caption_len = od_snprintf(caption, sizeof(caption), "transaction_%.6g", quantiles[i]); rc = kiwi_be_write_row_description_add( msg, 0, caption, caption_len, 0, 0, 23 /* INT4OID */, 4, 0, 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } } } td_histogram_t *transactions_hgram = NULL; td_histogram_t *queries_hgram = NULL; if (extended) { transactions_hgram = td_new(QUANTILES_COMPRESSION); queries_hgram = td_new(QUANTILES_COMPRESSION); } void *argv[] = { stream, &extended, quantiles, &quantiles_count, transactions_hgram, queries_hgram }; rc = od_router_foreach(router, od_console_show_pools_add_cb, argv); if (rc == NOT_OK_RESPONSE) { goto error; } if (extended) { int offset; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { goto error; } char *aggregated_name = "aggregated"; rc = kiwi_be_write_data_row_add(stream, offset, aggregated_name, strlen(aggregated_name)); if (rc == NOT_OK_RESPONSE) { goto error; } rc = kiwi_be_write_data_row_add(stream, offset, aggregated_name, strlen(aggregated_name)); if (rc == NOT_OK_RESPONSE) { goto error; } const size_t rest_columns_count = 14; for (size_t i = 0; i < rest_columns_count; ++i) { rc = kiwi_be_write_data_row_add(stream, offset, NULL, NULL_MSG_LEN); if (rc == NOT_OK_RESPONSE) { goto error; } } rc = od_console_show_quantiles(stream, offset, quantiles_count, quantiles, transactions_hgram, queries_hgram); if (rc == NOT_OK_RESPONSE) { goto error; } } td_safe_free(transactions_hgram); td_safe_free(queries_hgram); return kiwi_be_write_complete(stream, "SHOW", 5); error: td_safe_free(transactions_hgram); td_safe_free(queries_hgram); return NOT_OK_RESPONSE; } static inline int od_console_show_servers_server_cb(od_server_t *server, void **argv) { od_route_t *route = server->route; int offset; machine_msg_t *stream = argv[0]; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } /* type */ char data[64]; size_t data_len; od_client_t *client = server->client; if (client != NULL && client->type == OD_POOL_CLIENT_INTERNAL) { data_len = od_snprintf(data, sizeof(data), "SI"); } else { data_len = od_snprintf(data, sizeof(data), "S"); } int rc; rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* user */ rc = kiwi_be_write_data_row_add(stream, offset, route->id.user, route->id.user_len - 1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* database */ rc = kiwi_be_write_data_row_add(stream, offset, route->id.database, route->id.database_len - 1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* state */ char *state = ""; if (server->state == OD_SERVER_IDLE) { state = "idle"; } else if (server->state == OD_SERVER_ACTIVE) { state = "active"; } rc = kiwi_be_write_data_row_add(stream, offset, state, strlen(state)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* addr */ if (server->io.io != NULL) { od_getpeername(server->io.io, data, sizeof(data), 1, 0); data_len = strlen(data); } else { strcpy(data, "N/A"); data_len = sizeof("N/A"); } rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* port */ if (server->io.io != NULL) { od_getpeername(server->io.io, data, sizeof(data), 0, 1); data_len = strlen(data); } else { strcpy(data, "N/A"); data_len = sizeof("N/A"); } rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* local_addr */ if (server->io.io != NULL) { od_getsockname(server->io.io, data, sizeof(data), 1, 0); data_len = strlen(data); } else { strcpy(data, "N/A"); data_len = sizeof("N/A"); } rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* local_port */ if (server->io.io != NULL) { od_getsockname(server->io.io, data, sizeof(data), 0, 1); data_len = strlen(data); } else { strcpy(data, "N/A"); data_len = sizeof("N/A"); } rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* connect_time */ rc = kiwi_be_write_data_row_add(msg, offset, NULL, -1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* request_time */ rc = kiwi_be_write_data_row_add(msg, offset, NULL, -1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* wait */ data_len = od_snprintf(data, sizeof(data), "0"); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* wait_us */ data_len = od_snprintf(data, sizeof(data), "0"); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* ptr */ data_len = od_snprintf(data, sizeof(data), "%s%.*s", server->id.id_prefix, (signed)sizeof(server->id.id), server->id.id); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* link */ data_len = od_snprintf(data, sizeof(data), "%s", ""); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* remote_pid */ data_len = od_snprintf(data, sizeof(data), "0"); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* tls */ data_len = od_snprintf(data, sizeof(data), "%s", server->endpoint->storage->tls_opts->tls); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* offline */ data_len = od_snprintf(data, sizeof(data), "%d", server->offline); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return 0; } static inline int od_console_show_fds_server_cb(od_server_t *server, void **argv) { int offset; int mmask; machine_msg_t *stream = argv[0]; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } /* type */ char data[64]; size_t data_len; data_len = od_snprintf(data, sizeof(data), "S"); int rc; rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* ptr */ data_len = od_snprintf(data, sizeof(data), "%s%.*s", server->id.id_prefix, (signed)sizeof(server->id.id), server->id.id); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* system fd */ data_len = od_snprintf(data, sizeof(data), "%d", machine_io_sysfd(server->io.io)); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* machine fd mask */ mmask = machine_io_sysfd(server->io.io); data_len = od_snprintf(data, sizeof(data), "%s/%s", mmask & 1 ? "R" : "NOR", mmask & 2 ? "W" : "NOW"); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return 0; } static inline int show_server_pstmt_cb_internal(mm_hashmap_t *server_map, mm_hashmap_kvp_t *kvp, void **argv) { od_server_t *server = argv[1]; od_route_t *route = server->route; int offset; machine_msg_t *stream = argv[0]; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { goto error; } /* type */ char data[64]; size_t data_len; data_len = od_snprintf(data, sizeof(data), "S"); int rc; rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* user */ rc = kiwi_be_write_data_row_add(stream, offset, route->id.user, route->id.user_len - 1); if (rc == NOT_OK_RESPONSE) { goto error; } /* database */ rc = kiwi_be_write_data_row_add(stream, offset, route->id.database, route->id.database_len - 1); if (rc == NOT_OK_RESPONSE) { goto error; } /* sid */ data_len = od_snprintf(data, sizeof(data), "%s%.*s", server->id.id_prefix, (signed)sizeof(server->id.id), server->id.id); rc = kiwi_be_write_data_row_add(msg, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } const od_pstmt_t *pstmt = od_server_pstmt_kvp_pstmt(server_map, kvp); /* name */ rc = kiwi_be_write_data_row_add(stream, offset, pstmt->name, strlen(pstmt->name)); if (rc == NOT_OK_RESPONSE) { goto error; } /* description */ rc = kiwi_be_write_data_row_add(stream, offset, pstmt->desc.data, pstmt->desc.len); if (rc == NOT_OK_RESPONSE) { goto error; } /* refcount */ data_len = od_snprintf(data, sizeof(data), "%d", 0); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } return OK_RESPONSE; error: return NOT_OK_RESPONSE; } static inline int od_console_show_server_prep_stmt_cb(od_server_t *server, void **argv) { mm_hashmap_t *hm = server->prep_stmts; void *eargv[2] = { argv[0], (void *)server, }; int rc = mm_hashmap_foreach(hm, show_server_pstmt_cb_internal, eargv); return rc; } static inline int od_console_show_servers_cb(od_route_t *route, void **argv) { od_route_lock(route); od_route_server_pool_foreach_locked(route, OD_SERVER_ACTIVE, od_console_show_servers_server_cb, argv); od_route_server_pool_foreach_locked( route, OD_SERVER_IDLE, od_console_show_servers_server_cb, argv); od_route_unlock(route); return 0; } static inline int od_console_show_fds_cb(od_route_t *route, void **argv) { od_route_lock(route); od_route_server_pool_foreach_locked( route, OD_SERVER_ACTIVE, od_console_show_fds_server_cb, argv); od_route_server_pool_foreach_locked( route, OD_SERVER_IDLE, od_console_show_fds_server_cb, argv); od_route_unlock(route); return 0; } static inline int od_console_show_server_prep_stmts_cb(od_route_t *route, void **argv) { od_route_lock(route); od_route_server_pool_foreach_locked(route, OD_SERVER_ACTIVE, od_console_show_server_prep_stmt_cb, argv); od_route_server_pool_foreach_locked(route, OD_SERVER_IDLE, od_console_show_server_prep_stmt_cb, argv); od_route_unlock(route); return 0; } static inline int od_console_show_servers(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf( stream, "sssssdsdssddssdss", "type", "user", "database", "state", "addr", "port", "local_addr", "local_port", "connect_time", "request_time", "wait", "wait_us", "ptr", "link", "remote_pid", "tls", "offline"); if (msg == NULL) { return NOT_OK_RESPONSE; } void *argv[] = { stream }; od_router_foreach(router, od_console_show_servers_cb, argv); return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_fds(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "ssds", "type", "ptr", "machine fd", "machine fd mask"); if (msg == NULL) { return NOT_OK_RESPONSE; } void *argv[] = { stream }; od_router_foreach(router, od_console_show_fds_cb, argv); return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_server_prep_stmts(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "sssssss", "type", "user", "database", "sid", "name", "definition", "refcount"); if (msg == NULL) { return NOT_OK_RESPONSE; } void *argv[] = { stream }; od_router_foreach(router, od_console_show_server_prep_stmts_cb, argv); return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int show_pstmt_cb(const od_pstmt_t *pstmt, void *arg) { machine_msg_t *stream, *msg; char data[64]; size_t data_len; int rc, offset; stream = arg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { goto error; } /* name */ data_len = od_snprintf(data, sizeof(data), "%s", pstmt->name); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != 0) { goto error; } /* description */ rc = kiwi_be_write_data_row_add(stream, offset, pstmt->desc.data, pstmt->desc.len); if (rc != 0) { goto error; } /* refcount */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, atomic_load_explicit(&pstmt->refs, memory_order_acquire)); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != 0) { goto error; } return 0; error: return 1; } static inline int od_console_show_prep_stmts(od_client_t *client, machine_msg_t *stream) { od_instance_t *instance; machine_msg_t *msg; od_assert(stream); instance = client->global->instance; msg = kiwi_be_write_row_descriptionf(stream, "sss", "name", "definition", "refcount"); if (msg == NULL) { return NOT_OK_RESPONSE; } od_global_pstmt_foreach(instance->pstmts, show_pstmt_cb, (void *)stream); return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_is_paused(od_client_t *client, machine_msg_t *stream) { int offset; machine_msg_t *msg; char data; msg = kiwi_be_write_row_descriptionf(stream, "b", "is_paused"); if (msg == NULL) { return NOT_OK_RESPONSE; } if (kiwi_be_write_data_row(stream, &offset) == NULL) { return NOT_OK_RESPONSE; } data = od_global_is_paused(client->global) ? 't' : 'f'; int rc = kiwi_be_write_data_row_add(stream, offset, &data, 1); if (rc != OK_RESPONSE) { return rc; } return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_host_utilization(od_client_t *client, machine_msg_t *stream) { int offset; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "ff", "cpu", "mem"); if (msg == NULL) { return NOT_OK_RESPONSE; } if (kiwi_be_write_data_row(stream, &offset) == NULL) { return NOT_OK_RESPONSE; } float cpu, mem; od_global_read_host_utilization(client->global, &cpu, &mem); char data[16]; snprintf(data, sizeof(data), "%.2f", cpu); int rc = kiwi_be_write_data_row_add(stream, offset, data, strlen(data)); if (rc != OK_RESPONSE) { return rc; } snprintf(data, sizeof(data), "%.2f", mem); rc = kiwi_be_write_data_row_add(stream, offset, data, strlen(data)); if (rc != OK_RESPONSE) { return rc; } return kiwi_be_write_complete(stream, "HOST_UTILIZATION", sizeof("HOST_UTILIZATION")); } static inline int od_console_show_config_add(machine_msg_t *stream, char *key, char *value, char *fallback, char *changeable) { int offset; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } int rc; /* key */ rc = kiwi_be_write_data_row_add(stream, offset, key, strlen(key)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* value */ rc = kiwi_be_write_data_row_add(stream, offset, value, strlen(value)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* default */ rc = kiwi_be_write_data_row_add(stream, offset, fallback, strlen(fallback)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* changeable */ rc = kiwi_be_write_data_row_add(stream, offset, changeable, strlen(changeable)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return 0; } /* * SHOW CONFIG * * Reports the global configuration of the running process: the value * currently in effect, the built-in default, and whether the parameter * is picked up by RELOAD. Reading odyssey.conf shows what was asked * for, not what is in effect - od_config_reload() copies only part of * the parsed configuration into the running instance. */ static inline int od_console_show_config(od_client_t *client, machine_msg_t *stream, const char *field_name) { od_assert(stream); od_instance_t *instance = client->global->instance; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "ssss", "key", "value", "default", "changeable"); if (msg == NULL) { return NOT_OK_RESPONSE; } size_t count = od_config_fields_count(); for (size_t i = 0; i < count; i++) { const od_config_field_t *field = od_config_field_at(i); if (field == NULL) { return NOT_OK_RESPONSE; } if (field_name != NULL && strcmp(field->key, field_name) != 0) { continue; } char value[OD_CONFIG_FIELD_VALUE_MAX]; char fallback[OD_CONFIG_FIELD_VALUE_MAX]; od_config_field_value(&instance->config, field, value, sizeof(value)); od_config_field_default(field, fallback, sizeof(fallback)); int rc = od_console_show_config_add( stream, field->key, value, fallback, field->reloadable ? "yes" : "no"); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } } return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_rules(machine_msg_t *stream) { int offset; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "ssssb", "database", "user", "address", "connection_type", "obsolete"); if (msg == NULL) { return NOT_OK_RESPONSE; } od_global_t *global = od_global_get(); od_router_t *router = global->router; od_rules_t *rules = &router->rules; od_rules_lock(rules); od_list_t *i, *n; od_list_foreach_safe (&rules->rules, i, n) { od_rule_t *rule = od_container_of(i, od_rule_t, link); msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { goto error; } char data[128]; int data_len, rc; if (rule->db_is_default) { data_len = od_snprintf(data, sizeof(data), "%s", ""); } else { data_len = od_snprintf(data, sizeof(data), "%.*s", rule->db_name_len, rule->db_name); } rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } if (rule->user_is_default) { data_len = od_snprintf(data, sizeof(data), "%s", ""); } else { data_len = od_snprintf(data, sizeof(data), "%.*s", rule->user_name_len, rule->user_name); } rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } if (rule->address_range.is_default) { data_len = od_snprintf(data, sizeof(data), "%s", ""); } else { data_len = od_snprintf( data, sizeof(data), "%.*s", rule->address_range.string_value_len, rule->address_range.string_value); } rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } data_len = od_snprintf(data, sizeof(data), "%s", od_rule_conn_type_to_str(rule->conn_type)); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } data_len = od_snprintf(data, sizeof(data), "%s", rule->obsolete ? "t" : "f"); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } } od_rules_unlock(rules); return kiwi_be_write_complete(stream, "SHOW", sizeof("SHOW")); error: od_rules_unlock(rules); return NOT_OK_RESPONSE; } static inline int od_console_show_clients_callback(od_client_t *client, void **argv) { /* Odyssey should not expose its internal routes to SHOW utilities */ if (client->type == OD_POOL_CLIENT_INTERNAL) { return 0; } int offset; machine_msg_t *stream = argv[0]; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } char data[64]; size_t data_len; /* type */ data_len = od_snprintf(data, sizeof(data), "C"); int rc; rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* user */ rc = kiwi_be_write_data_row_add(stream, offset, client->startup.user.value, client->startup.user.value_len - 1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* database */ rc = kiwi_be_write_data_row_add(stream, offset, client->startup.database.value, client->startup.database.value_len - 1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* state */ char *state = ""; if (client->state == OD_CLIENT_ACTIVE) { state = "active"; } else if (client->state == OD_CLIENT_PENDING) { state = "pending"; } else if (client->state == OD_CLIENT_QUEUE) { state = "queue"; } rc = kiwi_be_write_data_row_add(stream, offset, state, strlen(state)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* storage_user */ rc = kiwi_be_write_data_row_add(stream, offset, client->rule->storage_user, client->rule->storage_user_len - 1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* addr */ od_getpeername(client->io.io, data, sizeof(data), 1, 0); data_len = strlen(data); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* port */ od_getpeername(client->io.io, data, sizeof(data), 0, 1); data_len = strlen(data); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* local_addr */ od_getsockname(client->io.io, data, sizeof(data), 1, 0); data_len = strlen(data); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* local_port */ od_getsockname(client->io.io, data, sizeof(data), 0, 1); data_len = strlen(data); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* connect_time */ rc = kiwi_be_write_data_row_add(stream, offset, NULL, -1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* request_time */ rc = kiwi_be_write_data_row_add(stream, offset, NULL, -1); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* wait */ data_len = od_snprintf(data, sizeof(data), "0"); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* wait_us */ data_len = od_snprintf(data, sizeof(data), "0"); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* id */ data_len = od_snprintf(data, sizeof(data), "%s%.*s", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* ptr */ data_len = od_snprintf(data, sizeof(data), "%p", (void *)client); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* coro */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, client->coroutine_id); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* remote_pid */ data_len = od_snprintf(data, sizeof(data), "0"); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* tls */ data_len = od_snprintf(data, sizeof(data), "%s", ""); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return 0; } static inline od_retcode_t od_console_show_clients_cb(od_route_t *route, void **argv) { od_route_lock(route); od_client_pool_foreach(&route->client_pool, OD_CLIENT_ACTIVE, od_console_show_clients_callback, argv); od_client_pool_foreach(&route->client_pool, OD_CLIENT_PENDING, od_console_show_clients_callback, argv); od_client_pool_foreach(&route->client_pool, OD_CLIENT_QUEUE, od_console_show_clients_callback, argv); od_route_unlock(route); return 0; } static inline int od_console_show_clients(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf( stream, "ssssssdsdssddssdds", "type", "user", "database", "state", "storage_user", "addr", "port", "local_addr", "local_port", "connect_time", "request_time", "wait", "wait_us", "id", "ptr", "coro", "remote_pid", "tls"); if (msg == NULL) { return NOT_OK_RESPONSE; } void *argv[] = { stream }; od_router_foreach(router, od_console_show_clients_cb, argv); return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_lists_add(machine_msg_t *stream, char *list, int items) { int offset; machine_msg_t *msg; msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } /* list */ int rc; rc = kiwi_be_write_data_row_add(stream, offset, list, strlen(list)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* items */ char data[64]; int data_len; data_len = od_snprintf(data, sizeof(data), "%d", items); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return 0; } static inline int od_console_show_lists_cb(od_route_t *route, void **argv) { od_route_lock(route); int *used_servers = argv[0]; int *free_servers = argv[1]; (*used_servers) += od_route_server_pool_count_active_locked( route, 1 /* only for route's db.user */); (*free_servers) += od_route_server_pool_count_idle_locked( route, 1 /* only for route's db.user */); od_route_unlock(route); return 0; } static inline int od_console_show_lists(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; /* Gather router information. router_used_servers can be inconsistent here, since it depends on separate route locks. */ od_router_lock(router); int router_used_servers = 0; int router_free_servers = 0; int router_pools = router->route_pool.count; int router_clients = (int)atomic_load_explicit(&router->clients, memory_order_relaxed); void *argv[] = { &router_used_servers, &router_free_servers }; od_route_pool_foreach(&router->route_pool, od_console_show_lists_cb, argv); od_router_unlock(router); machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "sd", "list", "items"); if (msg == NULL) { return NOT_OK_RESPONSE; } int rc; /* databases */ rc = od_console_show_lists_add(stream, "databases", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* users */ rc = od_console_show_lists_add(stream, "users", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* pools */ rc = od_console_show_lists_add(stream, "pools", router_pools); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* free_clients */ rc = od_console_show_lists_add(stream, "free_clients", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* used_clients */ rc = od_console_show_lists_add(stream, "used_clients", router_clients); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* login_clients */ rc = od_console_show_lists_add(stream, "login_clients", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* routing_clients */ rc = od_console_show_lists_add( stream, "routing_clients", (uint32_t)mm_sem_in_use(&client->global->routing_sem)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* free_servers */ rc = od_console_show_lists_add(stream, "free_servers", router_free_servers); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* used_servers */ rc = od_console_show_lists_add(stream, "used_servers", router_used_servers); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* dns_names */ rc = od_console_show_lists_add(stream, "dns_names", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* dns_zones */ rc = od_console_show_lists_add(stream, "dns_zones", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* dns_queries */ rc = od_console_show_lists_add(stream, "dns_queries", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* dns_pending */ rc = od_console_show_lists_add(stream, "dns_pending", 0); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return kiwi_be_write_complete(stream, "SHOW", 5); } /* * Report the state of the running process itself, as opposed to the objects * it is currently routing. */ static inline int od_console_show_instance(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_instance_t *instance = client->global->instance; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "sd", "list", "items"); if (msg == NULL) { return NOT_OK_RESPONSE; } int rc; /* config_load_failed */ rc = od_console_show_lists_add( stream, "config_load_failed", atomic_load(&instance->config_load_failed)); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_write_nullable_str(machine_msg_t *stream, int offset, char *str) { char data[64]; int data_len; if (str == NULL) { data_len = od_snprintf(data, sizeof(data), "(None)"); return kiwi_be_write_data_row_add(stream, offset, data, data_len); } return kiwi_be_write_data_row_add(stream, offset, str, strlen(str)); } static inline int od_console_show_tls_options(od_tls_opts_t *tls_opts, int offset, machine_msg_t *stream) { char *tls = od_config_tls_to_str(tls_opts->tls_mode); od_retcode_t rc; /* tls */ rc = od_console_write_nullable_str(stream, offset, tls); if (rc != OK_RESPONSE) { return rc; } /* tls_cert_file */ rc = od_console_write_nullable_str(stream, offset, tls_opts->tls_cert_file); if (rc != OK_RESPONSE) { return rc; } /* tls_key_file */ rc = od_console_write_nullable_str(stream, offset, tls_opts->tls_key_file); if (rc != OK_RESPONSE) { return rc; } /* tls_ca_file */ rc = od_console_write_nullable_str(stream, offset, tls_opts->tls_ca_file); if (rc != OK_RESPONSE) { return rc; } /* tls_protocols */ rc = od_console_write_nullable_str(stream, offset, tls_opts->tls_protocols); if (rc != OK_RESPONSE) { return rc; } return OK_RESPONSE; } static inline int od_console_show_listen(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "sdsssss", "host", "port", "tls", "tls_cert_file", "tls_key_file", "tls_ca_file", "tls_protocols"); if (msg == NULL) { return NOT_OK_RESPONSE; } od_instance_t *instance = router->global->instance; od_config_t *config = &instance->config; char data[64]; int data_len; int rc; int offset; od_list_t *i; od_list_foreach (&config->listen, i) { od_config_listen_t *listen_config; listen_config = od_container_of(i, od_config_listen_t, link); msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { return NOT_OK_RESPONSE; } /* host */ rc = od_console_write_nullable_str(stream, offset, listen_config->host); if (rc != OK_RESPONSE) { return rc; } /* port */ data_len = od_snprintf(data, sizeof(data), "%d", listen_config->port); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc != OK_RESPONSE) { return rc; } rc = od_console_show_tls_options(listen_config->tls_opts, offset, stream); if (rc != OK_RESPONSE) { return rc; } } return kiwi_be_write_complete(stream, "SHOW", 5); } static inline int od_console_show_storages(od_client_t *client, machine_msg_t *stream) { od_assert(stream); od_router_t *router = client->global->router; machine_msg_t *msg; msg = kiwi_be_write_row_descriptionf(stream, "ssdsssss", "type", "host", "port", "tls", "tls_cert_file", "tls_key_file", "tls_ca_file", "tls_protocols"); if (msg == NULL) { return NOT_OK_RESPONSE; } od_rules_t *rules = &router->rules; int rc; int offset; od_rules_lock(rules); od_list_t *i; od_list_foreach (&rules->storages, i) { od_rule_storage_t *storage; storage = od_container_of(i, od_rule_storage_t, link); msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { rc = NOT_OK_RESPONSE; goto error; } if (storage->storage_type == OD_RULE_STORAGE_REMOTE) { rc = kiwi_be_write_data_row_add(stream, offset, "remote", 6 + 1); if (rc == NOT_OK_RESPONSE) { goto error; } } else { rc = kiwi_be_write_data_row_add(stream, offset, "local", 5 + 1); if (rc == NOT_OK_RESPONSE) { goto error; } } char *host = storage->host; if (!host) { host = ""; } rc = kiwi_be_write_data_row_add(stream, offset, host, strlen(host)); if (rc == NOT_OK_RESPONSE) { goto error; } char data[64]; int data_len; /* port */ data_len = od_snprintf(data, sizeof(data), "%d", storage->port); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } rc = od_console_show_tls_options(storage->tls_opts, offset, stream); if (rc != OK_RESPONSE) { goto error; } } od_rules_unlock(rules); return kiwi_be_write_complete(stream, "SHOW", 5); error: od_rules_unlock(rules); return rc; } typedef enum { OD_SHOW_INVALID = 0, OD_SHOW_STATS, OD_SHOW_HELP, OD_SHOW_POOLS, OD_SHOW_POOLS_EXTENDED, OD_SHOW_DATABASES, OD_SHOW_SERVERS, OD_SHOW_SERVER_PREP_STMTS, OD_SHOW_GLOBAL_PREPARED_STMTS, OD_SHOW_CLIENTS, OD_SHOW_LISTS, OD_SHOW_INSTANCE, OD_SHOW_ERRORS, OD_SHOW_ERRORS_PER_ROUTE, OD_SHOW_VERSION, OD_SHOW_VERSION_EXTENDED, OD_SHOW_LISTEN, OD_SHOW_STORAGES, OD_SHOW_FDS, OD_SHOW_IS_PAUSED, OD_SHOW_HOST_UTILIZATION, OD_SHOW_RULES, OD_SHOW_CONFIG, } od_show_target_id_t; static od_show_target_id_t od_console_show_target_lookup(const char *name) { static const struct { const char *name; od_show_target_id_t id; } map[] = { { "stats", OD_SHOW_STATS }, { "help", OD_SHOW_HELP }, { "pools", OD_SHOW_POOLS }, { "pools_extended", OD_SHOW_POOLS_EXTENDED }, { "databases", OD_SHOW_DATABASES }, { "servers", OD_SHOW_SERVERS }, { "server_prep_stmts", OD_SHOW_SERVER_PREP_STMTS }, { "global_prepared_statements", OD_SHOW_GLOBAL_PREPARED_STMTS }, { "clients", OD_SHOW_CLIENTS }, { "lists", OD_SHOW_LISTS }, { "instance", OD_SHOW_INSTANCE }, { "errors", OD_SHOW_ERRORS }, { "errors_per_route", OD_SHOW_ERRORS_PER_ROUTE }, { "version", OD_SHOW_VERSION }, { "version_extended", OD_SHOW_VERSION_EXTENDED }, { "listen", OD_SHOW_LISTEN }, { "storages", OD_SHOW_STORAGES }, { "fds", OD_SHOW_FDS }, { "is_paused", OD_SHOW_IS_PAUSED }, { "host_utilization", OD_SHOW_HOST_UTILIZATION }, { "rules", OD_SHOW_RULES }, { "config", OD_SHOW_CONFIG }, }; for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) { if (strcmp(name, map[i].name) == 0) { return map[i].id; } } return OD_SHOW_INVALID; } static inline int od_console_show(od_client_t *client, machine_msg_t *stream, const char *name, const char *arg) { od_assert(stream); if (name == NULL) { return NOT_OK_RESPONSE; } switch (od_console_show_target_lookup(name)) { case OD_SHOW_STATS: return od_console_show_stats(client, stream); case OD_SHOW_HELP: return od_console_show_help(stream); case OD_SHOW_POOLS: return od_console_show_pools(client, stream, false); case OD_SHOW_POOLS_EXTENDED: return od_console_show_pools(client, stream, true); case OD_SHOW_DATABASES: return od_console_show_databases(client, stream); case OD_SHOW_SERVERS: return od_console_show_servers(client, stream); case OD_SHOW_SERVER_PREP_STMTS: return od_console_show_server_prep_stmts(client, stream); case OD_SHOW_GLOBAL_PREPARED_STMTS: return od_console_show_prep_stmts(client, stream); case OD_SHOW_CLIENTS: return od_console_show_clients(client, stream); case OD_SHOW_LISTS: return od_console_show_lists(client, stream); case OD_SHOW_INSTANCE: return od_console_show_instance(client, stream); case OD_SHOW_ERRORS: return od_console_show_errors(client, stream); case OD_SHOW_ERRORS_PER_ROUTE: return od_console_show_errors_per_route(client, stream); case OD_SHOW_VERSION: return od_console_show_version(stream); case OD_SHOW_VERSION_EXTENDED: return od_console_show_version_extended(stream); case OD_SHOW_LISTEN: return od_console_show_listen(client, stream); case OD_SHOW_STORAGES: return od_console_show_storages(client, stream); case OD_SHOW_FDS: return od_console_show_fds(client, stream); case OD_SHOW_IS_PAUSED: return od_console_show_is_paused(client, stream); case OD_SHOW_HOST_UTILIZATION: return od_console_show_host_utilization(client, stream); case OD_SHOW_RULES: return od_console_show_rules(stream); case OD_SHOW_CONFIG: return od_console_show_config(client, stream, arg); default: od_frontend_errorf(client, stream, KIWI_SYNTAX_ERROR, "unknown show target: %s", name); return 0; } } static inline int od_console_pause(od_client_t *client, machine_msg_t *stream) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "pause", client, NULL, "global pause is on"); od_global_pause(client->global); return kiwi_be_write_complete(stream, "PAUSE", 6); } static inline int od_console_resume(od_client_t *client, machine_msg_t *stream) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "pause", client, NULL, "global pause is off"); od_global_resume(client->global); return kiwi_be_write_complete(stream, "RESUME", 7); } static inline int od_console_kill_client(od_client_t *client, machine_msg_t *stream, const char *id_str) { (void)stream; od_id_t id; if (strlen(id_str) != (sizeof(id.id) + 1)) { return NOT_OK_RESPONSE; } memcpy(id.id, id_str + 1, sizeof(id.id)); od_router_kill(client->global->router, &id); return 0; } static inline int od_console_reload(od_client_t *client, machine_msg_t *stream) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "console", NULL, NULL, "RELOAD command received"); /* send reload request to system thread and wait for completion */ od_system_t *system = client->global->system; mm_wait_flag_t *done = mm_wait_flag_create(); if (done == NULL) { od_error(&instance->logger, "console", client, NULL, "failed to create wait flag for reload"); return -1; } int rc = od_system_send_msg(system, OD_MSG_RELOAD, done); if (rc != 0) { mm_wait_flag_destroy(done); od_error(&instance->logger, "console", client, NULL, "failed to send reload request to system thread"); return -1; } /* wait for system thread to finish reload */ mm_wait_flag_wait(done, UINT32_MAX); mm_wait_flag_destroy(done); return kiwi_be_write_complete(stream, "RELOAD", 7); } static inline int od_console_gc(od_client_t *client, machine_msg_t *stream) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "console", NULL, NULL, "GC command received"); /* send GC request to system thread and wait for completion */ od_system_t *system = client->global->system; mm_wait_flag_t *done = mm_wait_flag_create(); if (done == NULL) { od_error(&instance->logger, "console", client, NULL, "failed to create wait flag for GC"); return -1; } int rc = od_system_send_msg(system, OD_MSG_GC, done); if (rc != 0) { mm_wait_flag_destroy(done); od_error(&instance->logger, "console", client, NULL, "failed to send GC request to system thread"); return -1; } /* wait for system thread to finish GC */ mm_wait_flag_wait(done, UINT32_MAX); mm_wait_flag_destroy(done); return kiwi_be_write_complete(stream, "GC", 3); } static inline int od_console_set(od_client_t *client, machine_msg_t *stream) { (void)client; /* reply success */ return kiwi_be_write_complete(stream, "SET", 4); } static inline int od_console_drop_server_cb(od_server_t *server, void **argv) { (void)argv; server->offline = 1; return OK_RESPONSE; } static inline od_retcode_t od_console_drop_server(od_route_t *route, void **argv) { od_route_lock(route); od_route_server_pool_foreach_locked(route, OD_SERVER_ACTIVE, od_console_drop_server_cb, argv); od_route_server_pool_foreach_locked(route, OD_SERVER_IDLE, od_console_drop_server_cb, argv); od_route_unlock(route); return OK_RESPONSE; } static inline od_retcode_t od_console_drop_servers(od_client_t *client, machine_msg_t *stream) { (void)client; od_assert(stream); od_router_t *router = client->global->router; void *argv[] = { stream }; od_router_foreach(router, od_console_drop_server, argv); return OK_RESPONSE; } static void od_console_error_cb(const char *msg, void *userdata) { char *buf = (char *)userdata; if (buf == NULL || msg == NULL) { return; } snprintf(buf, 255, "%s", msg); } int od_console_query(od_client_t *client, machine_msg_t *stream, char *query_data, uint32_t query_data_size) { od_instance_t *instance = client->global->instance; uint32_t query_len; char *query; machine_msg_t *msg; if (client->rule->user_role != OD_RULE_ROLE_ADMIN && client->rule->user_role != OD_RULE_ROLE_STAT) { goto incorrect_role; } int rc; rc = kiwi_be_read_query(query_data, query_data_size, &query, &query_len); if (rc == NOT_OK_RESPONSE) { od_error(&instance->logger, "console", client, NULL, "bad console command"); msg = od_frontend_errorf(client, stream, KIWI_SYNTAX_ERROR, "bad console command"); if (msg == NULL) { return NOT_OK_RESPONSE; } return 0; } if (instance->config.log_query) { od_debug(&instance->logger, "console", client, NULL, "%.*s", query_len, query); } od_linear_alloc_t *arena = od_worker_get_local_linear_alloc(); char parse_err[256] = { 0 }; od_console_node_t *ast = od_console_parse( query, query_len - 1, arena, od_console_error_cb, parse_err); if (ast == NULL) { od_error(&instance->logger, "console", client, NULL, "console command error: %.*s", query_len - 1, query); if (parse_err[0] != '\0') { msg = od_frontend_errorf(client, stream, KIWI_SYNTAX_ERROR, "console command error: %s", parse_err); } else { msg = od_frontend_errorf(client, stream, KIWI_SYNTAX_ERROR, "console command error: %.*s", query_len - 1, query); } if (msg == NULL) { return NOT_OK_RESPONSE; } return 0; } int is_admin = (client->rule->user_role == OD_RULE_ROLE_ADMIN); int is_stat = (client->rule->user_role == OD_RULE_ROLE_STAT); switch (ast->type) { case OD_CONSOLE_NODE_TYPE_SHOW_STMT: { od_console_show_stmt_t *n = (od_console_show_stmt_t *)ast; rc = od_console_show(client, stream, n->name, n->arg); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; } case OD_CONSOLE_NODE_TYPE_KILL_CLIENT_STMT: { if (!is_admin) { goto incorrect_role; } od_console_kill_client_stmt_t *n = (od_console_kill_client_stmt_t *)ast; rc = od_console_kill_client(client, stream, n->id); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; } case OD_CONSOLE_NODE_TYPE_RELOAD_STMT: if (!is_admin) { goto incorrect_role; } rc = od_console_reload(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_CONSOLE_NODE_TYPE_GC_STMT: if (!is_admin) { goto incorrect_role; } rc = od_console_gc(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_CONSOLE_NODE_TYPE_SET_STMT: if (!is_admin) { goto incorrect_role; } rc = od_console_set(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_CONSOLE_NODE_TYPE_DROP_STMT: { if (!is_admin) { goto incorrect_role; } od_console_drop_stmt_t *n = (od_console_drop_stmt_t *)ast; switch (n->target) { case OD_CONSOLE_DROP_SERVERS: rc = od_console_drop_servers(client, stream); break; default: goto bad_query; } if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; } case OD_CONSOLE_NODE_TYPE_PAUSE_STMT: if (!is_admin) { goto incorrect_role; } rc = od_console_pause(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_CONSOLE_NODE_TYPE_RESUME_STMT: if (!is_admin) { goto incorrect_role; } rc = od_console_resume(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; default: goto bad_query; } (void)is_stat; return 0; incorrect_role: od_error(&instance->logger, "console", client, NULL, "Unsuitable user role to emit console command"); msg = od_frontend_errorf( client, stream, KIWI_INSUFFICIENT_PRIVILEGE, "Unsuitable user role to emit console command"); if (msg == NULL) { return NOT_OK_RESPONSE; } return 0; bad_query: od_error(&instance->logger, "console", client, NULL, "console command error: %.*s", query_len, query); msg = od_frontend_errorf(client, stream, KIWI_SYNTAX_ERROR, "console command error: %.*s", query_len, query); if (msg == NULL) { return NOT_OK_RESPONSE; } return 0; } odyssey-1.6.0-rc1/sources/console/000077500000000000000000000000001526064202200170245ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/console/ast.c000066400000000000000000000042321526064202200177600ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Console command parser AST helpers. */ #include #include #include #include od_console_node_t *od_console_node_alloc(od_linear_alloc_t *al, od_console_node_tag_t type, size_t size) { od_console_node_t *n = od_linear_alloc_alloc(al, size, OD_LINEAR_ALLOC_ZERO); if (n == NULL) { return NULL; } n->type = type; return n; } void od_console_node_free(od_console_node_t *node) { (void)node; } int od_console_node_print(const od_console_node_t *node, char *buf, size_t buflen) { if (node == NULL) { return snprintf(buf, buflen, "(null)"); } switch (node->type) { case OD_CONSOLE_NODE_TYPE_SHOW_STMT: { const od_console_show_stmt_t *n = (const od_console_show_stmt_t *)node; if (n->arg) { return snprintf(buf, buflen, "(show %s %s)", n->name ? n->name : "", n->arg); } return snprintf(buf, buflen, "(show %s)", n->name ? n->name : ""); } case OD_CONSOLE_NODE_TYPE_KILL_CLIENT_STMT: { const od_console_kill_client_stmt_t *n = (const od_console_kill_client_stmt_t *)node; return snprintf(buf, buflen, "(kill-client %s)", n->id); } case OD_CONSOLE_NODE_TYPE_RELOAD_STMT: return snprintf(buf, buflen, "(reload)"); case OD_CONSOLE_NODE_TYPE_PAUSE_STMT: return snprintf(buf, buflen, "(pause)"); case OD_CONSOLE_NODE_TYPE_RESUME_STMT: return snprintf(buf, buflen, "(resume)"); case OD_CONSOLE_NODE_TYPE_SET_STMT: { const od_console_set_stmt_t *n = (const od_console_set_stmt_t *)node; if (n->value) { return snprintf(buf, buflen, "(set %s=%s)", n->key ? n->key : "", n->value); } return snprintf(buf, buflen, "(set %s=default)", n->key ? n->key : ""); } case OD_CONSOLE_NODE_TYPE_DROP_STMT: { const od_console_drop_stmt_t *n = (const od_console_drop_stmt_t *)node; switch (n->target) { case OD_CONSOLE_DROP_SERVERS: return snprintf(buf, buflen, "(drop servers)"); } return snprintf(buf, buflen, "(drop unknown)"); } case OD_CONSOLE_NODE_TYPE_GC_STMT: return snprintf(buf, buflen, "(gc)"); default: return snprintf(buf, buflen, "(unknown:%d)", (int)node->type); } } odyssey-1.6.0-rc1/sources/console/ctx.c000066400000000000000000000010721526064202200177660ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Console command parser context. */ #include #include void od_console_parse_ctx_init(od_console_parse_ctx_t *ctx, const char *input, size_t input_len, od_linear_alloc_t *arena, od_console_error_cb_t error_cb, void *userdata) { memset(ctx, 0, sizeof(*ctx)); if (input_len == 0) { input_len = strlen(input); } ctx->scanbuf = input; ctx->scanbuflen = input_len; ctx->arena = arena; ctx->error_cb = error_cb; ctx->error_cb_userdata = userdata; } odyssey-1.6.0-rc1/sources/console/keywords.c000066400000000000000000000015621526064202200210430ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef struct { const char *name; int token; } od_console_keyword_t; static const od_console_keyword_t keywords[] = { /* top-level commands */ { "show", KW_SHOW }, { "kill_client", KW_KILL_CLIENT }, { "reload", KW_RELOAD }, { "pause", KW_PAUSE }, { "resume", KW_RESUME }, { "set", KW_SET }, { "drop", KW_DROP }, { "gc", KW_GC }, /* drop targets */ { "servers", KW_SERVERS }, /* set helpers */ { "to", KW_TO }, { "default", KW_DEFAULT }, { NULL, 0 } }; int od_console_keyword_lookup(const char *str, size_t len) { for (size_t i = 0; keywords[i].name != NULL; i++) { if (strlen(keywords[i].name) == len && strncasecmp(keywords[i].name, str, len) == 0) { return keywords[i].token; } } return 0; } odyssey-1.6.0-rc1/sources/console/parse.y000066400000000000000000000160561526064202200203400ustar00rootroot00000000000000%start top %define api.pure full %define api.prefix {od_console_yy} %define parse.error verbose %locations %parse-param { yyscan_t scanner } %parse-param { od_console_parse_ctx_t *ctx } %lex-param { yyscan_t scanner } %code top { #undef YYLLOC_DEFAULT #define YYLLOC_DEFAULT(cur, rhs, n) \ ((cur) = (n) ? YYRHSLOC(rhs, 1) : YYRHSLOC(rhs, 0)) } %code requires { #include #include #include #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; #endif typedef struct od_console_parse_ctx od_console_parse_ctx_t; #ifndef YYSTYPE #define YYSTYPE OD_CONSOLE_YYSTYPE #endif #undef YYLTYPE typedef int od_console_yyltype_t; #define YYLTYPE od_console_yyltype_t } %code { #undef YYLTYPE #undef OD_CONSOLE_YYLTYPE_IS_TRIVIAL #define OD_CONSOLE_YYLTYPE_IS_TRIVIAL 0 typedef int od_console_yyltype_t; #define YYLTYPE od_console_yyltype_t #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #include #include static void od_console_yyerror(YYLTYPE *loc, yyscan_t scanner, od_console_parse_ctx_t *ctx, const char *msg) { (void)loc; (void)scanner; ctx->had_error = 1; if (ctx->error_cb) { ctx->error_cb(msg, ctx->error_cb_userdata); } } static char *arena_str(od_console_parse_ctx_t *ctx, const char *src, size_t len) { char *s = od_linear_alloc_alloc(ctx->arena, len + 1, 0); if (s == NULL) { return NULL; } memcpy(s, src, len); s[len] = '\0'; return s; } static char *arena_strcat(od_console_parse_ctx_t *ctx, const char *a, size_t la, char sep, const char *b, size_t lb) { char *s = od_linear_alloc_alloc(ctx->arena, la + 1 + lb + 1, 0); if (s == NULL) { return NULL; } memcpy(s, a, la); s[la] = sep; memcpy(s + la + 1, b, lb); s[la + 1 + lb] = '\0'; return s; } #define ALLOC_NODE(ctx, type_name, enum_val) \ ((od_console_ ## type_name ## _stmt_t *) \ od_console_node_alloc((ctx)->arena, enum_val, \ sizeof(od_console_ ## type_name ## _stmt_t))) } %union { char *str; int64_t ival; od_console_node_t *node; } %token ERROR_TOKEN %token INTEGER %token SCONST %token IDENT /* top-level commands */ %token KW_SHOW %token KW_KILL_CLIENT %token KW_RELOAD %token KW_PAUSE %token KW_RESUME %token KW_SET %token KW_DROP %token KW_GC /* set helpers */ %token KW_TO %token KW_DEFAULT /* drop targets */ %token KW_SERVERS %type stmt %type show_stmt %type kill_client_stmt %type reload_stmt %type pause_stmt %type resume_stmt %type set_stmt %type drop_stmt %type gc_stmt %type set_key %type set_value %type col_id %type opt_show_arg %destructor { /* arena-allocated, no-op */ } %destructor { od_console_node_free($$); } %% top: stmt opt_semicolon { ctx->result = $1; } | opt_semicolon { ctx->result = NULL; } ; opt_semicolon: %empty | ';' ; stmt: show_stmt | kill_client_stmt | reload_stmt | pause_stmt | resume_stmt | set_stmt | drop_stmt | gc_stmt ; /* * SHOW [] * * Currently the optional is used by SHOW CONFIG * to filter the output to a single configuration parameter. */ show_stmt: KW_SHOW col_id opt_show_arg { od_console_show_stmt_t *n = ALLOC_NODE(ctx, show, OD_CONSOLE_NODE_TYPE_SHOW_STMT); if (n == NULL) YYABORT; n->name = $2; $2 = NULL; n->arg = $3; $3 = NULL; $$ = (od_console_node_t *)n; } ; opt_show_arg: %empty { $$ = NULL; } | col_id { $$ = $1; $1 = NULL; } ; /* * KILL_CLIENT */ kill_client_stmt: KW_KILL_CLIENT IDENT { if ($2 == NULL || strlen($2) != (size_t)(OD_ID_LEN + 1)) { od_console_yyerror(&yylloc, scanner, ctx, "invalid client id"); YYABORT; } od_console_kill_client_stmt_t *n = ALLOC_NODE(ctx, kill_client, OD_CONSOLE_NODE_TYPE_KILL_CLIENT_STMT); if (n == NULL) YYABORT; n->id = $2; $2 = NULL; $$ = (od_console_node_t *)n; } ; /* * RELOAD */ reload_stmt: KW_RELOAD { od_console_reload_stmt_t *n = ALLOC_NODE(ctx, reload, OD_CONSOLE_NODE_TYPE_RELOAD_STMT); if (n == NULL) YYABORT; $$ = (od_console_node_t *)n; } ; /* * PAUSE */ pause_stmt: KW_PAUSE { od_console_pause_stmt_t *n = ALLOC_NODE(ctx, pause, OD_CONSOLE_NODE_TYPE_PAUSE_STMT); if (n == NULL) YYABORT; $$ = (od_console_node_t *)n; } ; /* * RESUME */ resume_stmt: KW_RESUME { od_console_resume_stmt_t *n = ALLOC_NODE(ctx, resume, OD_CONSOLE_NODE_TYPE_RESUME_STMT); if (n == NULL) YYABORT; $$ = (od_console_node_t *)n; } ; /* * SET key [= | TO] [value | DEFAULT] * * The console SET command accepts key=value syntax. The value is * parsed but currently ignored by the console handler — it just * replies with success. */ set_stmt: KW_SET set_key '=' set_value { od_console_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_CONSOLE_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $2; $2 = NULL; n->value = $4; $4 = NULL; $$ = (od_console_node_t *)n; } | KW_SET set_key KW_TO set_value { od_console_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_CONSOLE_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $2; $2 = NULL; n->value = $4; $4 = NULL; $$ = (od_console_node_t *)n; } | KW_SET set_key '=' KW_DEFAULT { od_console_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_CONSOLE_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $2; $2 = NULL; n->value = NULL; $$ = (od_console_node_t *)n; } | KW_SET set_key KW_TO KW_DEFAULT { od_console_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_CONSOLE_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $2; $2 = NULL; n->value = NULL; $$ = (od_console_node_t *)n; } ; set_key: col_id { $$ = $1; } | set_key '.' col_id { size_t la = strlen($1), lb = strlen($3); $$ = arena_strcat(ctx, $1, la, '.', $3, lb); if ($$ == NULL) { od_console_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } ; set_value: SCONST { $$ = $1; $1 = NULL; } | col_id { $$ = $1; } | INTEGER { char tmp[24]; int n = snprintf(tmp, sizeof(tmp), "%" PRId64, $1); $$ = arena_str(ctx, tmp, (size_t)n); if ($$ == NULL) { od_console_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } ; /* * DROP SERVERS */ drop_stmt: KW_DROP KW_SERVERS { od_console_drop_stmt_t *n = ALLOC_NODE(ctx, drop, OD_CONSOLE_NODE_TYPE_DROP_STMT); if (n == NULL) YYABORT; n->target = OD_CONSOLE_DROP_SERVERS; n->path = NULL; $$ = (od_console_node_t *)n; } ; /* * GC */ gc_stmt: KW_GC { od_console_gc_stmt_t *n = ALLOC_NODE(ctx, gc, OD_CONSOLE_NODE_TYPE_GC_STMT); if (n == NULL) YYABORT; $$ = (od_console_node_t *)n; } ; col_id: IDENT { $$ = $1; $1 = NULL; } | KW_SERVERS { $$ = arena_str(ctx, "servers", 7); if ($$ == NULL) { od_console_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } ; %% odyssey-1.6.0-rc1/sources/console/parser.c000066400000000000000000000024641526064202200204720ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Console command parser entry point. */ #include #include #include #include #include od_console_node_t *od_console_parse(const char *input, size_t input_len, od_linear_alloc_t *arena, od_console_error_cb_t error_cb, void *userdata) { if (input == NULL) { return NULL; } od_linear_alloc_reset(arena, 0); od_console_parse_ctx_t ctx; od_console_parse_ctx_init(&ctx, input, input_len, arena, error_cb, userdata); ctx.fatal_set = 1; if (setjmp(ctx.fatal_jmp) != 0) { return NULL; } yyscan_t scanner; if (od_console_yylex_init_extra(&ctx, &scanner) != 0) { if (error_cb) { error_cb("failed to initialize scanner", userdata); } return NULL; } YY_BUFFER_STATE buf = od_console_yy_scan_bytes( ctx.scanbuf, (int)ctx.scanbuflen, scanner); if (buf == NULL) { if (error_cb) { error_cb("failed to create scanner buffer", userdata); } od_console_yylex_destroy(scanner); return NULL; } int rc = od_console_yyparse(scanner, &ctx); od_console_yy_delete_buffer(buf, scanner); od_console_yylex_destroy(scanner); if (rc != 0 || ctx.had_error) { od_console_node_free(ctx.result); return NULL; } return ctx.result; } odyssey-1.6.0-rc1/sources/console/scan.l000066400000000000000000000071501526064202200201300ustar00rootroot00000000000000%option reentrant %option bison-bridge %option bison-locations %option noyywrap %option nodefault %option warn %option never-interactive %option noyyalloc %option noyyrealloc %option noyyfree %option prefix="od_console_yy" %option extra-type="od_console_parse_ctx_t *" %top{ /* flex-generated code triggers sign-compare warnings */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" #include #include #include #include #include #define PG_NAMEDATALEN 64 #define YY_FATAL_ERROR(msg) do { \ od_console_parse_ctx_t *_ctx = od_console_yyget_extra(yyscanner); \ if (_ctx && _ctx->fatal_set) { \ _ctx->had_error = 1; \ if (_ctx->error_cb) { \ _ctx->error_cb(msg, _ctx->error_cb_userdata); \ } \ longjmp(_ctx->fatal_jmp, 1); \ } \ fprintf(stderr, "%s\n", msg); \ od_release_assert(0); \ } while (0) } %{ #define SET_YYLLOC() \ (*yylloc = (int)(yytext - yyextra->scanbuf)) void *od_console_yyalloc(yy_size_t bytes, yyscan_t yyscanner); void *od_console_yyrealloc(void *ptr, yy_size_t bytes, yyscan_t yyscanner); void od_console_yyfree(void *ptr, yyscan_t yyscanner); %} DIGIT [0-9] IDENT_START [A-Za-z_] IDENT_CONT [A-Za-z0-9_] IDENT {IDENT_START}{IDENT_CONT}* INTEGER -?{DIGIT}+ %% [ \t\r\n]+ { SET_YYLLOC(); /* skip */ } "--".* { SET_YYLLOC(); /* skip line comment */ } "/*" { SET_YYLLOC(); int c; while ((c = input(yyscanner)) != 0) { if (c == '*') { int next = input(yyscanner); if (next == 0) break; if (next == '/') goto comment_done; unput(next); } } comment_done:; } ";" { SET_YYLLOC(); return ';'; } "=" { SET_YYLLOC(); return '='; } "." { SET_YYLLOC(); return '.'; } {INTEGER} { SET_YYLLOC(); errno = 0; int64_t v = strtoll(yytext, NULL, 10); if (errno == ERANGE) { return ERROR_TOKEN; } yylval->ival = v; return INTEGER; } \'([^\'\\]|\\.)*\' { SET_YYLLOC(); size_t len = (size_t)yyleng - 2; od_linear_alloc_t *_arena = yyextra->arena; char *s = od_linear_alloc_alloc(_arena, len + 1, 0); if (s == NULL) { return ERROR_TOKEN; } memcpy(s, yytext + 1, len); s[len] = '\0'; yylval->str = s; return SCONST; } \"([^\"\\]|\\.)*\" { SET_YYLLOC(); size_t len = (size_t)yyleng - 2; if (len > PG_NAMEDATALEN - 1) { return ERROR_TOKEN; } od_linear_alloc_t *_arena = yyextra->arena; char *s = od_linear_alloc_alloc(_arena, len + 1, 0); if (s == NULL) { return ERROR_TOKEN; } memcpy(s, yytext + 1, len); s[len] = '\0'; yylval->str = s; return IDENT; } {IDENT} { SET_YYLLOC(); int tok = od_console_keyword_lookup(yytext, (size_t)yyleng); if (tok != 0) { return tok; } od_linear_alloc_t *_arena = yyextra->arena; char *s = od_linear_alloc_alloc(_arena, (size_t)yyleng + 1, 0); if (s == NULL) { return ERROR_TOKEN; } for (int _i = 0; _i < yyleng; _i++) { s[_i] = (char)tolower(yytext[_i]); } s[yyleng] = '\0'; yylval->str = s; return IDENT; } . { SET_YYLLOC(); return ERROR_TOKEN; } %% void * od_console_yyalloc(yy_size_t bytes, yyscan_t yyscanner) { od_console_parse_ctx_t *ctx = od_console_yyget_extra(yyscanner); return od_linear_alloc_alloc(ctx->arena, (size_t)bytes, OD_LINEAR_ALLOC_ZERO); } void * od_console_yyrealloc(void *ptr, yy_size_t bytes, yyscan_t yyscanner) { (void)ptr; (void)bytes; (void)yyscanner; abort(); } void od_console_yyfree(void *ptr, yyscan_t yyscanner) { (void)ptr; (void)yyscanner; } odyssey-1.6.0-rc1/sources/counter.c000066400000000000000000000033001526064202200172010ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include static inline void check_value(od_counter_t *counter, size_t value) { if (od_unlikely(value >= counter->size)) { /* no need any special action, it is an error about constant sizes */ abort(); } } od_counter_t *od_counter_create(size_t max_value) { if (max_value > OD_COUNTER_MAX_POSSIBLE_VALUE) { /* * counter should be used only for some * small ranges, like enum for status */ abort(); } /* 0..max_value */ size_t values_count = max_value + 1; od_counter_t *counter = od_malloc(sizeof(od_counter_t) + sizeof(atomic_uint_fast64_t) * values_count); if (od_unlikely(counter == NULL)) { return NULL; } counter->size = values_count; for (size_t i = 0; i < counter->size; ++i) { atomic_store(&counter->value_to_count[i], 0ULL); } return counter; } od_retcode_t od_counter_free(od_counter_t *counter) { od_free(counter); return OK_RESPONSE; } void od_counter_inc(od_counter_t *counter, size_t value) { check_value(counter, value); atomic_fetch_add(&counter->value_to_count[value], 1); } uint64_t od_counter_get_count(od_counter_t *counter, size_t value) { check_value(counter, value); return atomic_load(&counter->value_to_count[value]); } od_retcode_t od_counter_reset(od_counter_t *counter, size_t value) { check_value(counter, value); atomic_store(&counter->value_to_count[value], 0ULL); return OK_RESPONSE; } od_retcode_t od_counter_reset_all(od_counter_t *counter) { for (size_t i = 0; i < counter->size; ++i) { atomic_store(&counter->value_to_count[i], 0ULL); } return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/cron.c000066400000000000000000000235671526064202200165040ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include static int od_cron_stat_cb(od_route_t *route, od_stat_t *current, od_stat_t *avg, #ifdef PROM_FOUND od_prom_metrics_t *metrics, #endif void **argv) { od_instance_t *instance = argv[0]; (void)current; struct { int database_len; char database[64]; int user_len; char user[64]; int obsolete; int client_pool_total; int server_pool_active; int server_pool_idle; uint64_t avg_count_tx; uint64_t avg_tx_time; uint64_t avg_count_query; uint64_t avg_query_time; uint64_t avg_recv_client; uint64_t avg_recv_server; } info; od_route_lock(route); info.database_len = od_snprintf(info.database, sizeof(info.database), "%s", route->rule->db_name); info.user_len = od_snprintf(info.user, sizeof(info.user), "%s", route->rule->user_name); info.obsolete = route->rule->obsolete; info.client_pool_total = od_client_pool_total(&route->client_pool); info.server_pool_active = od_route_server_pool_count_active_locked( route, 1 /* only for route's db.user */); info.server_pool_idle = od_route_server_pool_count_idle_locked( route, 1 /* only for route's db.user */); info.avg_count_query = avg->count_query; info.avg_count_tx = avg->count_tx; info.avg_query_time = avg->query_time; info.avg_tx_time = avg->tx_time; info.avg_recv_server = avg->recv_server; info.avg_recv_client = avg->recv_client; od_route_unlock(route); #ifdef PROM_FOUND if (instance->config.log_general_stats_prom) { od_prom_metrics_write_stat_cb( metrics, info.user, info.database, info.database_len, info.user_len, info.client_pool_total, info.server_pool_active, info.server_pool_idle, info.avg_count_tx, info.avg_tx_time, info.avg_count_query, info.avg_query_time, info.avg_recv_client, info.avg_recv_server); if (instance->config.log_route_stats_prom) { char *prom_log = (char *)od_prom_metrics_get_stat_cb(metrics); va_list empty_va = { 0 }; od_logger_write(&instance->logger, OD_LOG, "stats", NULL, NULL, prom_log, empty_va); od_free(prom_log); } } #endif od_log(&instance->logger, "stats", NULL, NULL, "[%.*s.%.*s%s] %d clients, " "%d active servers, " "%d idle servers, " "%" PRIu64 " transactions/sec (%" PRIu64 " usec) " "%" PRIu64 " queries/sec (%" PRIu64 " usec) " "%" PRIu64 " in bytes/sec, " "%" PRIu64 " out bytes/sec", info.database_len, info.database, info.user_len, info.user, info.obsolete ? " obsolete" : "", info.client_pool_total, info.server_pool_active, info.server_pool_idle, info.avg_count_tx, info.avg_tx_time, info.avg_count_query, info.avg_query_time, info.avg_recv_client, info.avg_recv_server); return 0; } static inline void send_msg_stat(machine_channel_t *chan) { machine_msg_t *msg; msg = machine_msg_create(0); machine_msg_set_type(msg, OD_MSG_STAT); machine_channel_write(chan, msg); } static inline void request_logger_stats(od_logger_t *logger) { od_logger_stat(logger); } static inline void request_worker_stats(od_worker_pool_t *worker_pool) { uint32_t i; for (i = 0; i < worker_pool->count; i++) { od_worker_t *worker = &worker_pool->pool[i]; send_msg_stat(worker->task_channel); } } static inline void od_cron_stat(od_cron_t *cron) { od_router_t *router = cron->global->router; od_instance_t *instance = cron->global->instance; od_worker_pool_t *worker_pool = cron->global->worker_pool; /* snapshot log_stats to avoid race on config reload (SIGHUP) */ int log_stats = instance->config.log_stats; if (log_stats) { /* system worker stats */ uint64_t count_coroutine = 0; uint64_t count_coroutine_cache = 0; uint64_t msg_allocated = 0; uint64_t msg_cache_count = 0; uint64_t msg_cache_gc_count = 0; uint64_t msg_cache_size = 0; uint64_t startup_errors = atomic_exchange(&cron->startup_errors, 0); machine_stat(&count_coroutine, &count_coroutine_cache, &msg_allocated, &msg_cache_count, &msg_cache_gc_count, &msg_cache_size); #ifdef PROM_FOUND if (instance->config.log_general_stats_prom) { od_prom_metrics_write_stat( cron->metrics, msg_allocated, msg_cache_count, msg_cache_gc_count, msg_cache_size, count_coroutine, count_coroutine_cache); char *prom_log = (char *)od_prom_metrics_get_stat(cron->metrics); va_list empty_va = { 0 }; od_logger_write(&instance->logger, OD_LOG, "stats", NULL, NULL, prom_log, empty_va); od_free(prom_log); } #endif od_log(&instance->logger, "stats", NULL, NULL, "system worker: msg (%" PRIu64 " allocated, %" PRIu64 " cached, %" PRIu64 " freed, %" PRIu64 " cache_size), " "coroutines (%" PRIu64 " active, %" PRIu64 " cached) startup errors %" PRIu64, msg_allocated, msg_cache_count, msg_cache_gc_count, msg_cache_size, count_coroutine, count_coroutine_cache, startup_errors); /* request stats per worker */ request_worker_stats(worker_pool); request_logger_stats(&instance->logger); od_log(&instance->logger, "stats", NULL, NULL, "clients %d", (int)atomic_load_explicit(&router->clients, memory_order_relaxed)); } /* update stats per route and print info */ od_route_pool_stat_cb_t stat_cb; if (!log_stats) { stat_cb = NULL; } else { stat_cb = od_cron_stat_cb; } void *argv[] = { instance }; od_router_stat(router, cron->stat_time_us, #ifdef PROM_FOUND cron->metrics, #endif stat_cb, argv); /* update current stat time mark */ cron->stat_time_us = machine_time_us(); } static inline void od_cron_keep_min_pool_sizes(od_cron_t *cron) { od_router_t *router = cron->global->router; od_router_keep_min_pool_size_step(router); } static inline void od_cron_expire(od_cron_t *cron) { od_router_t *router = cron->global->router; od_instance_t *instance = cron->global->instance; /* collect and close expired idle servers */ od_list_t expire_list; od_list_init(&expire_list); /* * TODO: close connections in some other thread? * Now closing several connections at once can take some long time (why?) * and lead to pool_ttl or server_lifetime violation on the order of a seconds */ int rc; rc = od_router_expire(router, &expire_list); if (rc > 0) { od_list_t *i, *n; od_list_foreach_safe (&expire_list, i, n) { od_server_t *server; server = od_container_of(i, od_server_t, link); od_debug(&instance->logger, "expire", NULL, server, "closing idle server connection (%d secs)", server->idle_time); server->route = NULL; od_backend_close_connection(server); od_backend_close(server); } } /* cleanup unused dynamic or obsolete routes */ od_router_gc(router); } void od_cron_expire_now(od_cron_t *cron) { od_cron_expire(cron); } static void od_cron_err_stat(od_cron_t *cron) { od_router_t *router = cron->global->router; od_list_t *it; od_list_foreach (&router->route_pool.list, it) { od_route_t *current_route = od_container_of(it, od_route_t, link); od_route_lock(current_route); { if (current_route->extra_logging_enabled) { od_err_logger_inc_interval( current_route->err_logger); } } od_route_unlock(current_route); } od_router_lock(router); { od_err_logger_inc_interval(router->router_err_logger); } od_router_unlock(router); od_route_pool_lock(&router->route_pool); { od_err_logger_inc_interval(router->route_pool.err_logger); } od_route_pool_unlock(&router->route_pool); } static void od_cron(void *arg) { od_cron_t *cron = arg; od_instance_t *instance = cron->global->instance; cron->stat_time_us = machine_time_us(); int stats_tick = 0; for (;;) { /* mark and sweep expired idle server connections */ od_cron_expire(cron); /* create server connections if pool size is less than min_pool_size */ od_cron_keep_min_pool_sizes(cron); /* update statistics */ if (++stats_tick >= instance->config.stats_interval) { od_cron_stat(cron); stats_tick = 0; } od_cron_err_stat(cron); int rc = machine_wait_flag_wait(cron->online, 1000); if (rc == 0) { od_log(&instance->logger, "cron", NULL, NULL, "cron online is set, exiting"); break; } if (rc == -1 && machine_errno() != ETIMEDOUT) { od_error(&instance->logger, "cron", NULL, NULL, "can't wait online flag: %s (%d)", strerror(machine_errno()), machine_errno()); break; } } /* * a wait flag is used to prevent usage of routes * that are deallocated during shutdown */ machine_wait_flag_set(cron->can_be_freed); } od_retcode_t od_cron_init(od_cron_t *cron) { cron->stat_time_us = 0; cron->global = NULL; cron->startup_errors = 0; #ifdef PROM_FOUND cron->metrics = (od_prom_metrics_t *)od_malloc(sizeof(od_prom_metrics_t)); cron->metrics->port = 0; cron->metrics->http_server = NULL; #endif cron->online = machine_wait_flag_create(); if (cron->online == NULL) { return -1; } cron->can_be_freed = machine_wait_flag_create(); if (cron->can_be_freed == NULL) { machine_wait_flag_destroy(cron->online); return -1; } return 0; } int od_cron_start(od_cron_t *cron, od_global_t *global) { cron->global = global; od_instance_t *instance = global->instance; int64_t coroutine_id; coroutine_id = machine_coroutine_create(od_cron, cron); if (coroutine_id == INVALID_COROUTINE_ID) { od_error(&instance->logger, "cron", NULL, NULL, "failed to start cron coroutine"); return NOT_OK_RESPONSE; } return OK_RESPONSE; } od_retcode_t od_cron_stop(od_cron_t *cron) { machine_wait_flag_set(cron->online); machine_wait_flag_wait(cron->can_be_freed, UINT32_MAX); #ifdef PROM_FOUND od_prom_metrics_destroy(cron->metrics); #endif machine_wait_flag_destroy(cron->can_be_freed); machine_wait_flag_destroy(cron->online); return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/daemon.c000066400000000000000000000010241526064202200167660ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include int od_daemonize(void) { pid_t pid = fork(); if (pid < 0) { return -1; } if (pid > 0) { /* shutdown parent */ od_systemd_notify_mainpid(pid); _exit(0); } setsid(); int fd; fd = open("/dev/null", O_RDWR); if (fd < 0) { return -1; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) { close(fd); } return 0; } odyssey-1.6.0-rc1/sources/deploy.c000066400000000000000000000032661526064202200170310ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include static inline int complete_deploy(od_instance_t *instance, od_server_t *server, char *context) { uint32_t timeout = instance->config.backend_connect_timeout_ms; if (od_service_stream_server_until_rfq( context, server, 0 /* ignore errors */, timeout) != OD_OK) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } int od_deploy(od_client_t *client, char *context) { od_instance_t *instance = client->global->instance; od_server_t *server = client->server; if (client->physical_rep || client->logical_rep) { return 0; } /* compare and set options which are differs from server */ int query_count; query_count = 0; char query[OD_QRY_MAX_SZ]; int query_size; query_size = kiwi_vars_cas(&client->vars, &server->vars, query, sizeof(query) - 1, instance->config.smart_search_path_enquoting); if (query_size > 0) { query[query_size] = 0; query_size++; machine_msg_t *msg; msg = kiwi_fe_write_query(NULL, query, query_size); if (msg == NULL) { return -1; } int rc; rc = od_write(&server->io, msg); if (rc == -1) { return -1; } query_count++; client->server->synced_settings = false; od_debug(&instance->logger, context, client, server, "deploy: %s", query); } else { client->server->synced_settings = true; } if (query_count > 0) { od_server_sync_request(server, query_count); return complete_deploy(instance, server, context); } return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/dns.c000066400000000000000000000044451526064202200163210ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include int od_getsockaddrname(struct sockaddr *sa, char *buf, int size, int add_addr, int add_port) { char addr[128]; if (sa->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; inet_ntop(sa->sa_family, &sin->sin_addr, addr, sizeof(addr)); if (add_addr && add_port) { od_snprintf(buf, size, "%s:%d", addr, ntohs(sin->sin_port)); } else if (add_addr) { od_snprintf(buf, size, "%s", addr); } else if (add_port) { od_snprintf(buf, size, "%d", ntohs(sin->sin_port)); } return 0; } if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa; inet_ntop(sa->sa_family, &sin->sin6_addr, addr, sizeof(addr)); if (add_addr && add_port) { od_snprintf(buf, size, "[%s]:%d", addr, ntohs(sin->sin6_port)); } else if (add_addr) { od_snprintf(buf, size, "%s", addr); } else if (add_port) { od_snprintf(buf, size, "%d", ntohs(sin->sin6_port)); } return 0; } if (sa->sa_family == AF_UNIX) { if (!add_addr && add_port) { od_snprintf(buf, size, "-1"); } else { od_snprintf(buf, size, ""); } return 0; } od_snprintf(buf, size, "%s", ""); return -1; } int od_getaddrname(mm_addrinfo_t *ai, char *buf, int size, int add_addr, int add_port) { return od_getsockaddrname(ai->ai_addr, buf, size, add_addr, add_port); } int od_getpeername(mm_io_t *io, char *buf, int size, int add_addr, int add_port) { struct sockaddr_storage sa; int salen = sizeof(sa); int rc = machine_getpeername(io, (struct sockaddr *)&sa, &salen); if (rc < 0) { od_snprintf(buf, size, "%s", ""); return -1; } return od_getsockaddrname((struct sockaddr *)&sa, buf, size, add_addr, add_port); } int od_getsockname(mm_io_t *io, char *buf, int size, int add_addr, int add_port) { struct sockaddr_storage sa; int salen = sizeof(sa); int rc = machine_getsockname(io, (struct sockaddr *)&sa, &salen); if (rc < 0) { od_snprintf(buf, size, "%s", ""); return -1; } return od_getsockaddrname((struct sockaddr *)&sa, buf, size, add_addr, add_port); } odyssey-1.6.0-rc1/sources/duration.c000066400000000000000000000044521526064202200173600ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include /* * od_duration_parse -- * * Pure string implementation — see duration.h for the full specification. * * The loop reads one component per iteration. When a * component has no recognised suffix the bare number is scaled by * default_unit_us and the loop stops (backward-compatible bare-integer * behaviour). */ int od_duration_parse(const char *str, size_t len, int64_t default_unit_us, uint64_t *out_us) { const char *p = str; const char *end = str + len; uint64_t total = 0; bool first = true; /* skip leading whitespace */ while (p < end && isspace((unsigned char)*p)) { p++; } for (;;) { if (p >= end || !isdigit((unsigned char)*p)) { if (first) { return -1; } break; } /* read decimal integer */ uint64_t value = 0; while (p < end && isdigit((unsigned char)*p)) { value = value * 10 + (uint64_t)((unsigned char)*p++ - '0'); } first = false; /* read suffix (letters only) */ const char *sfx = p; while (p < end && isalpha((unsigned char)*p)) { p++; } int sfx_len = (int)(p - sfx); if (sfx_len == 0) { total += value * (uint64_t)default_unit_us; break; } if (sfx_len == 1 && tolower((unsigned char)sfx[0]) == 'd') { total += value * 86400000000ULL; } else if (sfx_len == 1 && tolower((unsigned char)sfx[0]) == 'h') { total += value * 3600000000ULL; } else if (sfx_len == 1 && tolower((unsigned char)sfx[0]) == 'm') { total += value * 60000000ULL; } else if (sfx_len == 1 && tolower((unsigned char)sfx[0]) == 's') { total += value * 1000000ULL; } else if (sfx_len == 2 && tolower((unsigned char)sfx[0]) == 'm' && tolower((unsigned char)sfx[1]) == 's') { total += value * 1000ULL; } else if (sfx_len == 2 && tolower((unsigned char)sfx[0]) == 'u' && tolower((unsigned char)sfx[1]) == 's') { total += value; } else if (sfx_len == 2 && tolower((unsigned char)sfx[0]) == 'n' && tolower((unsigned char)sfx[1]) == 's') { total += value / 1000ULL; } else { /* unrecognised suffix — stop and treat as bare number */ p = sfx; total += value * (uint64_t)default_unit_us; break; } } *out_us = total; return (int)(p - str); } odyssey-1.6.0-rc1/sources/duration.h000066400000000000000000000032201526064202200173550ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include /* * od_duration_parse -- * * Parse a Go-style duration string from a raw byte buffer. * No dependency on od_parser_t — this is a pure string function. * * Recognised suffixes (case-insensitive): * d days (86400 s) * h hours (3600 s) * m minutes (60 s) * s seconds * ms milliseconds * us microseconds * ns nanoseconds (truncated to microseconds) * * Compound expressions like "1h30m" or "1d2h3m4s" are supported. * * If a component carries no recognised suffix, the bare number is multiplied * by default_unit_us (in microseconds) for backward compatibility: * default_unit_us = 1000 — bare number means milliseconds * default_unit_us = 1000000 — bare number means seconds * * Leading whitespace is consumed and included in the returned count. * * Returns: number of bytes consumed (>= 1) on success, * -1 if the input does not start with a digit (after whitespace). * On success *out_us holds the total duration in microseconds. */ int od_duration_parse(const char *str, size_t len, int64_t default_unit_us, uint64_t *out_us); /* Bare number = milliseconds; result in microseconds. */ static inline int od_duration_parse_ms(const char *str, size_t len, uint64_t *out_us) { return od_duration_parse(str, len, 1000LL, out_us); } /* Bare number = seconds; result in microseconds. */ static inline int od_duration_parse_s(const char *str, size_t len, uint64_t *out_us) { return od_duration_parse(str, len, 1000000LL, out_us); } odyssey-1.6.0-rc1/sources/ejection.c000066400000000000000000000040511526064202200173260ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include static inline int queue_inc_ptr(od_conn_eject_info *info, int ptr) { ++ptr; if (ptr >= info->limit) { ptr = 0; } return ptr; } static inline void queue_pop(od_conn_eject_info *info) { if (info->size == 0) { return; } info->head = queue_inc_ptr(info, info->head); --info->size; } static inline int queue_push(od_conn_eject_info *info, uint64_t now_ms) { if (info->size == info->limit) { return 0; } info->queue[info->tail] = now_ms; info->tail = queue_inc_ptr(info, info->tail); ++info->size; return 1; } static inline void queue_clean(od_conn_eject_info *info, uint64_t now_ms) { while (info->size > 0) { if (info->queue[info->head] + (uint64_t)info->interval_ms > now_ms) { break; } queue_pop(info); } } od_retcode_t od_conn_eject_info_init(od_conn_eject_info **info, const od_config_conn_drop_options_t *opts) { *info = (od_conn_eject_info *)od_malloc(sizeof(od_conn_eject_info)); if (*info == NULL) { /* TODO: set errno properly */ return NOT_OK_RESPONSE; } memset(*info, 0, sizeof(od_conn_eject_info)); mm_spinlock_init(&(*info)->mu); (*info)->head = 0; (*info)->tail = 0; (*info)->size = 0; (*info)->interval_ms = opts->interval_ms; (*info)->limit = opts->rate; if ((*info)->limit <= 0) { return OK_RESPONSE; } (*info)->queue = od_malloc((*info)->limit * sizeof(uint64_t)); if ((*info)->queue == NULL) { od_free(*info); return NOT_OK_RESPONSE; } return OK_RESPONSE; } od_retcode_t od_conn_eject_info_free(od_conn_eject_info *info) { mm_spinlock_destroy(&info->mu); od_free(info->queue); od_free(info); return OK_RESPONSE; } int od_conn_eject_info_try(od_conn_eject_info *info, uint64_t now_ms) { int rc = 0; mm_spinlock_lock(&info->mu); if (info->limit > 0) { queue_clean(info, now_ms); rc = queue_push(info, now_ms); } else { rc = 1; } mm_spinlock_unlock(&info->mu); return rc; } odyssey-1.6.0-rc1/sources/err_logger.c000066400000000000000000000047611526064202200176650ustar00rootroot00000000000000#include #include #include #include static size_t err_logger_required_buf_size(size_t sz) { return sizeof(od_error_logger_t) + (sz * sizeof(od_counter_t *)); } #if defined(__x86_64__) || defined(__i386) || defined(_X86_) #define OD_ERR_LOGGER_CAS_BACKOFF __asm__("pause") #else #define OD_ERR_LOGGER_CAS_BACKOFF #endif od_error_logger_t *od_err_logger_create(size_t intervals_count) { od_error_logger_t *err_logger = od_malloc(err_logger_required_buf_size(intervals_count)); if (err_logger == NULL) { goto error; } err_logger->nintervals = intervals_count; atomic_init(&err_logger->current_interval_num, 0); for (size_t i = 0; i < intervals_count; ++i) { /* * used for router and frontend statuses * so, let it be 100 */ err_logger->interval_counters[i] = od_counter_create(100UL); if (err_logger->interval_counters[i] == NULL) { goto error; } } return err_logger; error: if (err_logger) { for (size_t i = 0; i < err_logger->nintervals; ++i) { if (err_logger->interval_counters[i] == NULL) { continue; } od_counter_free(err_logger->interval_counters[i]); } od_free((void *)(err_logger)); } return NULL; } od_retcode_t od_err_logger_free(od_error_logger_t *err_logger) { if (err_logger == NULL) { return OK_RESPONSE; } for (size_t i = 0; i < err_logger->nintervals; ++i) { if (err_logger->interval_counters[i] == NULL) { continue; } int rc = od_counter_free(err_logger->interval_counters[i]); err_logger->interval_counters[i] = NULL; if (rc != OK_RESPONSE) { return rc; } } od_free((void *)(err_logger)); return OK_RESPONSE; } od_retcode_t od_error_logger_store_err(od_error_logger_t *l, size_t err_t) { size_t idx = atomic_load(&l->current_interval_num); od_counter_inc(l->interval_counters[idx], err_t); return OK_RESPONSE; } od_retcode_t od_err_logger_inc_interval(od_error_logger_t *l) { while (1) { size_t i = atomic_load(&l->current_interval_num); size_t next = (i + 1) % l->nintervals; if (atomic_compare_exchange_weak(&l->current_interval_num, &i, next)) { /* some errors may be lost */ od_counter_reset_all(l->interval_counters[next]); break; } OD_ERR_LOGGER_CAS_BACKOFF; } return OK_RESPONSE; } size_t od_err_logger_get_aggr_errors_count(od_error_logger_t *l, size_t err_t) { size_t ret_val = 0; for (size_t i = 0; i < l->nintervals; ++i) { ret_val += od_counter_get_count(l->interval_counters[i], err_t); } return ret_val; } odyssey-1.6.0-rc1/sources/external_auth.c000066400000000000000000000124351526064202200203760ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #define AGENT_AUTH_OK 1 #define MAX_MSG_BODY_SIZE (1024 * 1024) #define TIMEOUT_MS 1000 #define DEFAULT_SOCKET_FILE "/tmp/external-auth.sock" static void write_header(void *dst, uint64_t header) { uint64_t le = htole64(header); memcpy(dst, &le, sizeof(header)); } static void read_header(uint64_t *header, const void *src) { uint64_t le; memcpy(&le, src, sizeof(uint64_t)); *header = le64toh(le); } machine_msg_t *msg_read(mm_io_t *io) { uint64_t body_size = 0; machine_msg_t *header = machine_read(io, sizeof(body_size), TIMEOUT_MS); if (header == NULL) { return NULL; } read_header(&body_size, machine_msg_data(header)); machine_msg_free(header); if (body_size > MAX_MSG_BODY_SIZE) { return NULL; } return machine_read(io, body_size, TIMEOUT_MS); } od_external_auth_status_t msg_write(mm_io_t *io, machine_msg_t *msg) { uint8_t *body = machine_msg_data(msg); uint64_t body_size = machine_msg_size(msg); machine_msg_t *w = machine_msg_create(sizeof(body_size) + body_size); if (w == NULL) { machine_msg_free(msg); return OD_EAUTH_ERROR; } uint8_t *pos = machine_msg_data(w); write_header(pos, body_size); memcpy(pos + sizeof(body_size), body, body_size); machine_msg_free(msg); int rc = machine_write(io, w, TIMEOUT_MS); if (rc < 0) { return OD_EAUTH_ERROR; } return OD_EAUTH_OK; } static machine_msg_t *build_cstr_msg(const char *str) { int len = strlen(str) + 1; machine_msg_t *msg = machine_msg_create(len); if (msg == NULL) { return NULL; } uint8_t *pos = machine_msg_data(msg); memcpy(pos, str, len); return msg; } static od_external_auth_status_t auth_impl(mm_io_t *io, const char *username, const char *token, od_instance_t *instance, od_client_t *client) { machine_msg_t *umsg = build_cstr_msg(username); if (umsg == NULL) { return OD_EAUTH_ERROR; } od_external_auth_status_t status = msg_write(io, umsg); if (status != OD_EAUTH_OK) { od_error(&instance->logger, "auth", client, NULL, "failed to send user msg to agent, errno=%d (%s)", machine_errno(), strerror(machine_errno())); return status; } machine_msg_t *tmsg = build_cstr_msg(token); if (tmsg == NULL) { return OD_EAUTH_ERROR; } status = msg_write(io, tmsg); if (status != OD_EAUTH_OK) { od_error(&instance->logger, "auth", client, NULL, "failed to send token msg to agent, errno=%d (%s)", machine_errno(), strerror(machine_errno())); return status; } machine_msg_t *resp = msg_read(io); if (resp == NULL) { od_error(&instance->logger, "auth", client, NULL, "failed to receive response from agent, errno=%d (%s)", machine_errno(), strerror(machine_errno())); return OD_EAUTH_ERROR; } if (machine_msg_size(resp) < 1) { od_error(&instance->logger, "auth", client, NULL, "invalid msg from agent"); machine_msg_free(resp); return OD_EAUTH_ERROR; } int success = ((char *)machine_msg_data(resp))[0] == AGENT_AUTH_OK; machine_msg_t *eid = msg_read(io); if (eid == NULL) { od_error( &instance->logger, "auth", client, NULL, "failed to receive external id from agent, errno=%d (%s)", machine_errno(), strerror(machine_errno())); machine_msg_free(resp); return OD_EAUTH_ERROR; } char *eid_str = machine_msg_data(eid); size_t eid_len = machine_msg_size(eid); client->external_id = od_malloc(eid_len + 1); if (client->external_id == NULL) { machine_msg_free(resp); machine_msg_free(eid); return OD_EAUTH_ERROR; } memcpy(client->external_id, eid_str, eid_len); client->external_id[eid_len] = '\0'; if (instance->config.log_session) { od_log(&instance->logger, "auth", client, NULL, "user '%s.%s' was authenticated with external_id: %s", client->startup.database.value, client->startup.user.value, client->external_id); } machine_msg_free(resp); machine_msg_free(eid); return success ? OD_EAUTH_OK : OD_EAUTH_DENIED; } od_external_auth_status_t external_user_authentication(char *username, char *token, od_instance_t *instance, od_client_t *client) { struct sockaddr *saddr; struct sockaddr_un exchange_socket; memset(&exchange_socket, 0, sizeof(exchange_socket)); exchange_socket.sun_family = AF_UNIX; saddr = (struct sockaddr *)&exchange_socket; if (instance->config.external_auth_socket_path == NULL) { od_snprintf(exchange_socket.sun_path, sizeof(exchange_socket.sun_path), "%s", DEFAULT_SOCKET_FILE); } else { od_snprintf(exchange_socket.sun_path, sizeof(exchange_socket.sun_path), "%s", instance->config.external_auth_socket_path); } od_external_auth_status_t status = OD_EAUTH_DENIED; mm_io_t *io = mm_io_create(); if (io == NULL) { return OD_EAUTH_ERROR; } int rc = mm_io_connect(io, saddr, TIMEOUT_MS); if (rc == 0) { status = auth_impl(io, username, token, instance, client); } else { od_error(&instance->logger, "auth", client, NULL, "failed to connect to %s, errno=%d (%s)", exchange_socket.sun_path, machine_errno(), strerror(machine_errno())); status = OD_EAUTH_ERROR; } mm_io_close(io); mm_io_free(io); return status; } odyssey-1.6.0-rc1/sources/frontend.c000066400000000000000000002527041526064202200173570ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline void od_frontend_close(od_client_t *client) { od_assert(client->route == NULL); od_assert(client->server == NULL); od_router_t *router = client->global->router; atomic_fetch_sub(&router->clients, 1); if (client->source != NULL) { atomic_fetch_sub(&client->source->clients, 1); } od_io_close(&client->io); od_client_free(client); } int od_frontend_info(od_client_t *client, char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_info_msg(client, NULL, fmt, args); va_end(args); if (msg == NULL) { return -1; } return od_write(&client->io, msg); } int od_frontend_error(od_client_t *client, char *code, char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_error_msg(client, NULL, code, fmt, args); va_end(args); if (msg == NULL) { return -1; } return od_write(&client->io, msg); } int od_frontend_fatal_no_startup(mm_io_t *io, char *code, char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_error_msg(NULL, NULL, code, fmt, args); va_end(args); if (msg == NULL) { return -1; } return machine_write(io, msg, UINT32_MAX); } int od_frontend_fatal(od_client_t *client, char *code, char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_fatal_msg(client, NULL, code, "", "", fmt, args); va_end(args); if (msg == NULL) { return -1; } return od_write(&client->io, msg); } int od_frontend_fatal_detailed(od_client_t *client, const char *code, const char *detail, const char *hint, const char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_fatal_msg(client, NULL, code, detail, hint, fmt, args); va_end(args); if (msg == NULL) { return -1; } return od_write(&client->io, msg); } static inline int od_frontend_error_fwd(od_client_t *client) { od_server_t *server = client->server; od_assert(server != NULL); od_assert(server->error_connect != NULL); kiwi_fe_error_t error; int rc; rc = kiwi_fe_read_error(machine_msg_data(server->error_connect), machine_msg_size(server->error_connect), &error); if (rc == -1) { return -1; } char text[512]; int text_len; text_len = od_snprintf(text, sizeof(text), "odyssey: %s%.*s: %s", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id, error.message); int detail_len = error.detail ? strlen(error.detail) : 0; int hint_len = error.hint ? strlen(error.hint) : 0; machine_msg_t *msg; msg = kiwi_be_write_error_as(NULL, error.severity, error.code, error.detail, detail_len, error.hint, hint_len, text, text_len); if (msg == NULL) { return -1; } return od_write(&client->io, msg); } static inline bool od_frontend_error_is_too_many_connections(od_client_t *client) { od_server_t *server = client->server; od_assert(server != NULL); if (server->error_connect == NULL) { return false; } kiwi_fe_error_t error; int rc; rc = kiwi_fe_read_error(machine_msg_data(server->error_connect), machine_msg_size(server->error_connect), &error); if (rc == -1) { return false; } return strcmp(error.code, KIWI_TOO_MANY_CONNECTIONS) == 0; } static int read_and_parse_startup(od_client_t *client, int *parse_rc) { od_instance_t *instance = client->global->instance; machine_msg_t *msg = od_read_startup( &client->io, client->source->config->client_login_timeout); if (msg == NULL) { if (parse_rc) { *parse_rc = KIWI_STARTUP_READ_LEN_ERROR; } return -1; } int rc = kiwi_be_read_startup(machine_msg_data(msg), machine_msg_size(msg) - 1 /* extra 0-terminator */, &client->startup, &client->vars); machine_msg_free(msg); if (parse_rc) { *parse_rc = rc; } if (rc != KIWI_STARTUP_READ_OK) { od_error(&instance->logger, "startup", client, NULL, "startup packet parse error, code = %d", rc); return -1; } return 0; } static void reject_unsupported_proto_version(od_client_t *client) { od_frontend_fatal_detailed( client, KIWI_FEATURE_NOT_SUPPORTED, "", "", "unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u", PG_PROTOCOL_MAJOR(client->startup.proto_version), PG_PROTOCOL_MINOR(client->startup.proto_version), PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST), PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST), PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)); } static int has_unsupported_features(od_client_t *client) { kiwi_var_t *pq_tpn = kiwi_vars_get( &client->vars, KIWI_VAR_PQ_TEST_PROTOCOL_NEGOTIATION); return pq_tpn != NULL; } static int send_negotiate(od_client_t *client) { int has_pq_tpn = kiwi_vars_get(&client->vars, KIWI_VAR_PQ_TEST_PROTOCOL_NEGOTIATION) != NULL; kiwi_vars_unset(&client->vars, KIWI_VAR_PQ_TEST_PROTOCOL_NEGOTIATION); const char **unsupported = (const char *[]){ "_pq_.test_protocol_negotiation" }; size_t nunsupported; if (has_pq_tpn) { nunsupported = 1; } else { nunsupported = 0; } machine_msg_t *msg = kiwi_be_write_protocol_negotiation( NULL, PG_PROTOCOL_LATEST, unsupported, nunsupported); if (msg == NULL) { return -1; } return od_write(&client->io, msg); } static int reject_unsupported_request(od_client_t *client) { od_instance_t *instance = client->global->instance; machine_msg_t *msg = machine_msg_create(sizeof(uint8_t)); if (msg == NULL) { return -1; } *(uint8_t *)machine_msg_data(msg) = 'N'; if (od_write(&client->io, msg) == -1) { od_error(&instance->logger, "unsupported protocol (gssapi)", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } od_debug(&instance->logger, "unsupported protocol (gssapi)", client, NULL, "ignoring"); return 0; } static int od_frontend_startup(od_client_t *client) { od_instance_t *instance = client->global->instance; int ssl_done = 0; int gss_done = 0; while (1) { int rc; int parse_rc; rc = read_and_parse_startup(client, &parse_rc); if (rc == -1) { if (parse_rc == KIWI_STARTUP_INVALID_MAJOR_ERROR) { reject_unsupported_proto_version(client); } goto error; } if (!client->startup.unsupported_request && !client->startup.is_ssl_request) { /* that was real startup/cancel packet - its ok */ break; } if (client->startup.is_ssl_request && !ssl_done) { if (od_tls_frontend_accept(client, &instance->logger, client->source->config, client->tls) == -1) { goto error; } client->startup.is_ssl_request = 0; ssl_done = 1; continue; } if (client->startup.unsupported_request && !gss_done) { if (reject_unsupported_request(client) == -1) { return -1; } gss_done = 1; continue; } od_error(&instance->logger, "startup", client, NULL, "unexpected repeated %s negotiation request", client->startup.is_ssl_request ? "ssl" : "gss"); goto error; } client->startup.is_ssl_request = ssl_done; if (client->startup.is_cancel) { /* no need to proceed any further */ return 0; } /* parse repl options */ if (client->startup.replication.value_len != 0) { if (strcmp(client->startup.replication.value, "database") == 0) { client->logical_rep = true; } else if (!parse_bool(client->startup.replication.value, &client->physical_rep)) { od_error( &instance->logger, "startup", client, NULL, "invalid value for parameter \"replication\" for client '%s'", client->peer); od_frontend_error( client, KIWI_CONNECTION_FAILURE, "invalid value for parameter \"replication\""); goto error; } } if (client->source->config->tls_opts->tls_mode >= OD_CONFIG_TLS_REQUIRE && !ssl_done) { od_log(&instance->logger, "tls", client, NULL, "required, closing"); od_frontend_error(client, KIWI_PROTOCOL_VIOLATION, "SSL is required"); return -1; } if (PG_PROTOCOL_MINOR(client->startup.proto_version) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) || has_unsupported_features(client)) { int rc = send_negotiate(client); if (rc != 0) { od_error(&instance->logger, "startup", client, NULL, "negotiation sending failed, errno=%d (%s)", mm_errno_get(), strerror(mm_errno_get())); goto error; } } if (od_compression_frontend_setup(client, client->source->config, &instance->logger) == -1) { od_error(&instance->logger, "startup", client, NULL, "can't setup compression, errno=%d (%s)", mm_errno_get(), strerror(mm_errno_get())); goto error; } return 0; error: od_debug(&instance->logger, "startup", client, NULL, "startup packet read error, errno = %d (%s)", machine_errno(), strerror(machine_errno())); od_cron_t *cron = client->global->cron; atomic_fetch_add(&cron->startup_errors, 1); return -1; } static inline int candidate_cmp_desc(const void *v1, const void *v2) { const od_endpoint_attach_candidate_t *c1 = v1; const od_endpoint_attach_candidate_t *c2 = v2; return c2->priority - c1->priority; } static inline int od_frontend_attach_candidate_get_priority( od_instance_t *instance, od_rule_storage_t *storage, od_endpoint_attach_candidate_t *candidate, od_target_session_attrs_t tsa, int prefer_localhost) { /* * priority of endpoints is determined by (from highest to lowest): * - prefer_localhost: for serice accounts connections * - az: we should use same az as of odyssey instance if it is possible * - tsa match: even if it is outdated, rw state of endpoint doesn't change frequently * - random number: to select host randomly between several suitable hosts * * so: * + 1000 priority by localhost address * + 500 priority by matched az * + 200 priority by matched tsa * + [0..100) shuffle coeff * * also negative priority will mean endpoints, that is not suitable, * ex: endpoints which read-write status certanly doesn't fit tsa */ od_storage_endpoint_t *endpoint = candidate->endpoint; int priority = 0; priority += machine_lrand48() % 100; od_storage_endpoint_status_t status; od_storage_endpoint_status_init(&status); od_storage_endpoint_status_get(&endpoint->status, &status); int status_is_recent = !od_storage_endpoint_status_is_outdated( &status, storage->endpoints_status_poll_interval_ms); int tsa_match = od_tsa_match_rw_state(tsa, status.is_read_write); if (status_is_recent && !tsa_match) { return -1; } if (status_is_recent && !status.alive) { return -1; } if (tsa_match) { priority += 200; } if (strcmp(instance->config.availability_zone, candidate->endpoint->address.availability_zone) == 0) { priority += 500; } if (prefer_localhost && od_address_is_localhost(&endpoint->address)) { priority += 1000; } return priority; } void od_frontend_attach_init_candidates( od_instance_t *instance, od_rule_storage_t *storage, od_endpoint_attach_candidate_t *candidates, od_target_session_attrs_t tsa, int prefer_localhost) { size_t count = storage->endpoints_count; for (size_t i = 0; i < count; ++i) { candidates[i].endpoint = &storage->endpoints[i]; candidates[i].priority = 0; } if (count == 1) { return; } for (size_t i = 0; i < count; ++i) { candidates[i].priority = od_frontend_attach_candidate_get_priority( instance, storage, &candidates[i], tsa, prefer_localhost); } qsort(candidates, count, sizeof(od_endpoint_attach_candidate_t), candidate_cmp_desc); } static inline od_frontend_status_t od_frontend_attach_to_endpoint( od_client_t *client, char *context, kiwi_params_t *route_params, od_storage_endpoint_t *endpoint, od_target_session_attrs_t tsa, int immediate, int is_deploy, od_rule_storage_t *storage) { od_instance_t *instance = client->global->instance; od_router_t *router = client->global->router; od_route_t *route = client->route; char addr[256]; bool wait_for_idle = false; for (;;) { od_router_status_t status; status = od_router_attach(router, client, wait_for_idle, endpoint, immediate); if (status != OD_ROUTER_OK) { if (immediate && status == OD_ROUTER_ERROR_TIMEDOUT) { return OD_EATTACH_TOO_MANY_CONNECTIONS; } if (status == OD_ROUTER_ERROR_TIMEDOUT) { od_error(&instance->logger, "router", client, NULL, "server pool wait timed out, closing"); return OD_EATTACH_TOO_MANY_CONNECTIONS; } return OD_EATTACH; } od_server_t *server = client->server; if (server->io.io && server->route->rule->pool->attach_check) { int rc = od_io_poll(&server->io); if (rc != 0) { od_error(&instance->logger, context, client, server, "server fd poll failed, errno=%d (%s)", machine_errno(), strerror(machine_errno())); } if (!od_io_connected(&server->io)) { od_log(&instance->logger, context, client, server, "server disconnected (%s), close connection and retry attach", od_io_error(&server->io)); od_router_close(router, client); continue; } } od_debug(&instance->logger, context, client, server, "client %s%.*s attached to %s%.*s", client->id.id_prefix, (int)sizeof(client->id.id), client->id.id, server->id.id_prefix, (int)sizeof(server->id.id), server->id.id); od_assert(od_server_synchronized(server)); /* connect to server, if necessary */ if (od_backend_not_connected(server)) { int rc; atomic_fetch_add(&router->servers_routing, 1); od_assert(client->source != NULL); rc = od_backend_connect(server, context, route_params, client, storage); atomic_fetch_sub(&router->servers_routing, 1); if (rc == NOT_OK_RESPONSE) { /* In case of 'too many connections' error, retry attach attempt by * waiting for a idle server connection for pool_timeout ms */ wait_for_idle = route->rule->pool->timeout > 0 && od_frontend_error_is_too_many_connections( client); if (wait_for_idle) { od_router_close(router, client); if (instance->config.server_login_retry) { machine_sleep( instance->config .server_login_retry); } continue; } od_storage_endpoint_status_set_dead( &endpoint->status); return OD_ESERVER_CONNECT; } } int rc = od_backend_startup_preallocated(server, route_params, client); if (rc != OK_RESPONSE) { od_storage_endpoint_status_set_dead(&endpoint->status); return OD_ESERVER_CONNECT; } od_tsa_check_result_t trc = od_backend_check_tsa( storage, endpoint, context, server, client, tsa); if (trc != OD_TSA_CHECK_OK) { od_address_to_str(&endpoint->address, addr, sizeof(addr) - 1); od_debug( &instance->logger, context, client, server, "read-write status of '%s' is mismatched with expected '%s' or failed to update", addr, od_target_session_attrs_to_str(tsa)); if (trc == OD_TSA_CHECK_BACKEND_ERROR) { od_router_close(router, client); } else { od_router_detach(router, client); } /* try another host */ return OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH; } if (is_deploy && client->rule->maintain_params) { int rc = od_deploy(client, context); if (rc == -1) { od_gerror("deploy", client, client->server, "deploy failed, errno=%d (%s)", mm_errno_get(), strerror(mm_errno_get())); return OD_ESERVER_WRITE; } } if (is_deploy && storage->balancing.debug_notice && storage->endpoints_count > 1) { od_address_to_str(&client->server->endpoint->address, addr, sizeof(addr) - 1); char msg[256]; od_snprintf(msg, sizeof(msg), "select host %s", addr); char buf[256]; int n = kiwi_be_format_notice(buf, sizeof(buf), 'M', msg); if (n != -1) { size_t unused; if (od_io_write_raw(&client->io, buf, n, &unused, 1000, 0) != 0) { return OD_ECLIENT_WRITE; } } } return OD_OK; } } static uint32_t get_effective_catchup_timeout(od_client_t *client) { uint32_t effective_timeout = 0; /* * next priorities to get the catchup timeout: * * var from pgoption > value from rule > value from listen */ od_config_listen_t *listen = client->source->config; int default_timeout = 0; if (listen != NULL) { default_timeout = listen->catchup_timeout; } if (client->rule->catchup_timeout != 0) { default_timeout = client->rule->catchup_timeout; } effective_timeout = (uint32_t)default_timeout; kiwi_var_t *timeout_var = kiwi_vars_get(&client->vars, KIWI_VAR_ODYSSEY_CATCHUP_TIMEOUT); if (timeout_var != NULL) { /* if there is catchup pgoption variable in startup packet */ char *end; uint32_t user_catchup_timeout = strtol(timeout_var->value, &end, 10); if (end == timeout_var->value + timeout_var->value_len) { /* if where is no junk after number, thats ok */ effective_timeout = user_catchup_timeout; } else { od_gerror("catchup", client, NULL, "junk after catchup timeout, ignore value"); } } return effective_timeout; } typedef struct { od_rule_storage_t *storage; od_target_session_attrs_t tsa; uint32_t lag_timeout; /* * For prefer-standby: when set, the primary (RW host) is filtered * out so that round-robin rotates over standbys only. Cleared on * the fallback pass to allow connecting to the primary. */ int standby_only; } host_select_arg_t; static int check_lag(uint32_t timeout, int64_t lag) { if (timeout == 0) { return 1; } if (lag < 0) { lag = 0; } return lag <= (int64_t)timeout; } static int host_filter(od_storage_endpoint_t *endpoint, void *a) { host_select_arg_t *arg = a; od_rule_storage_t *storage = arg->storage; od_target_session_attrs_t tsa = arg->tsa; od_storage_endpoint_status_t status; od_storage_endpoint_status_init(&status); od_storage_endpoint_status_get(&endpoint->status, &status); int status_is_recent = !od_storage_endpoint_status_is_outdated( &status, storage->endpoints_status_poll_interval_ms); if (status_is_recent) { int tsa_match = od_tsa_match_rw_state(tsa, status.is_read_write); int lag_match = check_lag(arg->lag_timeout, status.repl_lag_sec); if (!status.alive || !lag_match || !tsa_match) { return 0; } /* * For prefer-standby we want round-robin to rotate over * standbys (RO hosts) only, so that the balancing counter * advances by the number of standbys, not by the total * number of hosts (which would skew distribution towards * the standby preceding the primary in the list). * * The primary is kept as a fallback and is tried later via * a separate balancing_select pass without this filter. */ if (arg->standby_only && status.is_read_write) { return 0; } } /* lets try the host */ return 1; } static od_frontend_status_t attach_to_first(od_client_t *client, char *context, kiwi_params_t *route_params, od_storage_endpoint_t **endpoints, size_t count, od_target_session_attrs_t tsa, int fail_fast, int is_deploy, od_rule_storage_t *storage) { od_instance_t *instance = client->global->instance; od_frontend_status_t status = OD_EATTACH; for (size_t i = 0; i < count; ++i) { od_storage_endpoint_t *endpoint = endpoints[i]; char addr[256]; od_address_to_str(&endpoint->address, addr, sizeof(addr) - 1); od_debug(&instance->logger, context, client, NULL, "trying to attach to %s...", addr); /* * if client is attached now - previous attach failed * but the server is still in active state and attached to client * * so now need to detach the server from the client, * servers stays attached to client in case of error * to have an ability to perform error forwarding * * TODO: fix this way of forwarding the error */ if (client->server != NULL) { od_router_close(client->global->router, client); } status = od_frontend_attach_to_endpoint(client, context, route_params, endpoint, tsa, fail_fast, is_deploy, storage); if (status == OD_OK) { return status; } od_debug(&instance->logger, context, client, NULL, "attach to %s failed with status: %s", addr, od_frontend_status_to_str(status)); } return status; } static od_frontend_status_t attach_to_first_with_fail_fast(od_client_t *client, char *context, kiwi_params_t *route_params, od_storage_endpoint_t **endpoints, size_t count, od_target_session_attrs_t tsa, int is_deploy, od_rule_storage_t *storage) { int acquire_fail_fast = client->route->rule->pool->acquire_fail_fast && count > 1; /* * Per-client override: when set via SET odyssey.opportunistic_acquire, * never wait for a free connection — fail immediately with * "too many connections", even for a single host, and do not retry. */ int client_opportunistic = client->opportunistic_acquire; if (client_opportunistic) { acquire_fail_fast = 1; } od_frontend_status_t status = attach_to_first(client, context, route_params, endpoints, count, tsa, acquire_fail_fast, is_deploy, storage); if (status != OD_OK && acquire_fail_fast && !client_opportunistic) { /* * attach failed * * if this was an attempt to do it without awaiting - retry with awaiting * for connections from pool */ status = attach_to_first(client, context, route_params, endpoints, count, tsa, 0 /* wait for connection from pool */, is_deploy, storage); } return status; } static od_frontend_status_t attach_with_storage(od_client_t *client, char *context, kiwi_params_t *route_params, od_rule_storage_t *storage, int is_deploy) { od_route_t *route = client->route; od_target_session_attrs_t tsa = od_tsa_get_effective(client); uint32_t lag_timeout = get_effective_catchup_timeout(client); host_select_arg_t arg; arg.storage = storage; arg.tsa = tsa; arg.lag_timeout = lag_timeout; arg.standby_only = 0; od_storage_endpoint_t *endpoints[OD_STORAGE_MAX_ENDPOINTS]; od_storage_balancing_t *balancing = od_balancing_get_effective(client, storage); od_frontend_status_t status = OD_EATTACH; if (tsa == OD_TARGET_SESSION_ATTRS_PREFER_STANDBY) { /* * First pass: balance over standbys only so that the * round-robin counter advances by the number of * standbys (not by the total number of hosts), keeping * distribution even across replicas. */ arg.standby_only = 1; size_t count = od_storage_balancing_select( balancing, storage, route, endpoints, sizeof(endpoints) / sizeof(endpoints[0]), host_filter, &arg); if (count > 0) { status = attach_to_first_with_fail_fast( client, context, route_params, endpoints, count, tsa, is_deploy, storage); if (status == OD_OK) { return status; } } /* * Fallback: no standbys available or all standby * attempts failed — retry over all hosts (including the * primary) without the standby-only filter. */ arg.standby_only = 0; } size_t count = od_storage_balancing_select( balancing, storage, route, endpoints, sizeof(endpoints) / sizeof(endpoints[0]), host_filter, &arg); status = attach_to_first_with_fail_fast(client, context, route_params, endpoints, count, tsa, is_deploy, storage); return status; } static od_frontend_status_t attach_impl(od_client_t *client, char *context, kiwi_params_t *route_params, int is_deploy) { od_instance_t *instance = client->global->instance; od_config_listen_t *listen = client->source->config; if (listen != NULL && listen->storage_count > 0) { od_router_t *router = client->global->router; od_frontend_status_t status = OD_EATTACH; for (size_t i = 0; i < listen->storage_count; ++i) { const char *name = listen->storage_names[i]; od_rules_lock(&router->rules); od_rule_storage_t *storage = od_rules_storage_match(&router->rules, name); if (storage != NULL) { od_rules_storage_ref(storage); } od_rules_unlock(&router->rules); if (storage == NULL) { od_error(&instance->logger, context, client, NULL, "storage '%s' not found, skipping", name); continue; } if (storage->storage_type != client->route->rule->storage->storage_type) { od_debug( &instance->logger, context, client, NULL, "storage '%s' has mismatched storage_type, skipping", name); od_rules_storage_unref(storage); continue; } od_debug(&instance->logger, context, client, NULL, "trying storage '%s' (%zu/%zu)", name, i + 1, listen->storage_count); status = attach_with_storage(client, context, route_params, storage, is_deploy); od_rules_storage_unref(storage); if (status == OD_OK) { return OD_OK; } od_debug(&instance->logger, context, client, NULL, "storage '%s' failed with status: %s", name, od_frontend_status_to_str(status)); } return status; } return attach_with_storage(client, context, route_params, client->route->rule->storage, is_deploy); } od_frontend_status_t od_frontend_attach(od_client_t *client, char *context, kiwi_params_t *route_params) { return attach_impl(client, context, route_params, 0 /* is_deploy */); } od_frontend_status_t od_frontend_attach_and_deploy(od_client_t *client, char *context) { /* attach and maybe connect server, then deploy params */ return attach_impl(client, context, NULL, 1 /* is_deploy */); } static inline od_frontend_status_t od_frontend_setup_params(od_client_t *client) { od_instance_t *instance = client->global->instance; od_router_t *router = client->global->router; od_route_t *route = client->route; /* ensure route has cached server parameters */ int rc; rc = kiwi_params_lock_count(&route->params); if (rc == 0) { kiwi_params_t route_params; kiwi_params_init(&route_params); od_frontend_status_t status; status = od_frontend_attach(client, "setup", &route_params); if (status != OD_OK) { kiwi_params_free(&route_params); return status; } od_router_detach(router, client); /* There is possible race here, so we will discard our * attempt if params are already set */ rc = kiwi_params_lock_set_once(&route->params, &route_params); if (!rc) { kiwi_params_free(&route_params); } } od_debug(&instance->logger, "setup", client, NULL, "sending params:"); /* send parameters set by client or cached by the route */ kiwi_param_t *param = route->params.params.list; machine_msg_t *stream = machine_msg_create(0); if (stream == NULL) { return OD_EOOM; } while (param) { kiwi_var_type_t type; type = kiwi_vars_find(&client->vars, kiwi_param_name(param), param->name_len); kiwi_var_t *var; var = kiwi_vars_get(&client->vars, type); machine_msg_t *msg; if (var) { msg = kiwi_be_write_parameter_status(stream, var->name, var->name_len, var->value, var->value_len); od_debug(&instance->logger, "setup", client, NULL, " %.*s = %.*s", var->name_len, var->name, var->value_len, var->value); } else { msg = kiwi_be_write_parameter_status( stream, kiwi_param_name(param), param->name_len, kiwi_param_value(param), param->value_len); od_debug(&instance->logger, "setup", client, NULL, " %.*s = %.*s", param->name_len, kiwi_param_name(param), param->value_len, kiwi_param_value(param)); } if (msg == NULL) { machine_msg_free(stream); return OD_EOOM; } param = param->next; } rc = od_write(&client->io, stream); if (rc == -1) { return OD_ECLIENT_WRITE; } return OD_OK; } static inline od_frontend_status_t od_frontend_setup(od_client_t *client) { od_instance_t *instance = client->global->instance; od_route_t *route = client->route; /* set parameters */ od_frontend_status_t status; status = od_frontend_setup_params(client); if (status != OD_OK) { return status; } if (route->rule->pool->reserve_prepared_statement) { if (od_client_init_hm(client) != OK_RESPONSE) { od_log(&instance->logger, "setup", client, NULL, "failed to initialize hash map for prepared statements"); return OD_EOOM; } } /* write key data message */ machine_msg_t *stream; machine_msg_t *msg; msg = kiwi_be_write_backend_key_data(NULL, client->key.key_pid, client->key.key); if (msg == NULL) { return OD_EOOM; } stream = msg; /* write ready message */ msg = kiwi_be_write_ready(stream, 'I'); if (msg == NULL) { machine_msg_free(stream); return OD_EOOM; } int rc; rc = od_write(&client->io, stream); if (rc == -1) { return OD_ECLIENT_WRITE; } if (instance->config.log_session) { client->time_setup = machine_time_us(); od_log(&instance->logger, "setup", client, NULL, "login time: %" PRIu64 " microseconds", (client->time_setup - client->time_accept)); od_log(&instance->logger, "setup", client, NULL, "client connection from %s to route %s.%s accepted", client->peer, route->rule->db_name, route->rule->user_name); } return OD_OK; } static inline od_frontend_status_t od_frontend_local_setup(od_client_t *client) { machine_msg_t *stream; stream = machine_msg_create(0); if (stream == NULL) { goto error; } /* client parameters */ machine_msg_t *msg; char data[128]; int data_len; /* current version and build */ #ifdef ODYSSEY_VERSION_GIT data_len = od_snprintf(data, sizeof(data), "%s (git %s)", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT); #else data_len = od_snprintf(data, sizeof(data), "%s", ODYSSEY_VERSION_NUMBER); #endif msg = kiwi_be_write_parameter_status(stream, "server_version", 15, data, data_len + 1); if (msg == NULL) { goto error; } msg = kiwi_be_write_parameter_status(stream, "server_encoding", 16, "UTF-8", 6); if (msg == NULL) { goto error; } msg = kiwi_be_write_parameter_status(stream, "client_encoding", 16, "UTF-8", 6); if (msg == NULL) { goto error; } msg = kiwi_be_write_parameter_status(stream, "DateStyle", 10, "ISO", 4); if (msg == NULL) { goto error; } msg = kiwi_be_write_parameter_status(stream, "TimeZone", 9, "GMT", 4); if (msg == NULL) { goto error; } /* ready message */ msg = kiwi_be_write_ready(stream, 'I'); if (msg == NULL) { goto error; } int rc; rc = od_write(&client->io, stream); if (rc == -1) { return OD_ECLIENT_WRITE; } return OD_OK; error: if (stream) { machine_msg_free(stream); } return OD_EOOM; } static inline bool od_eject_conn_with_rate(od_client_t *client, od_server_t *server, od_instance_t *instance) { od_config_t *config = &instance->config; if (!config->conn_drop_options.drop_enabled) { return false; } if (server == NULL && client->rule->pool->pool_type == OD_RULE_POOL_SESSION) { od_log(&instance->logger, "shutdown", client, server, "drop client because it was never attached to server"); return true; } od_thread_global **gl = od_thread_global_get(); if (gl == NULL) { od_log(&instance->logger, "shutdown", client, server, "drop client connection on graceful shutdown, unable to throttle (wid %d)", (*gl)->wid); /* this is clearly something bad, TODO: handle properly */ return true; } od_conn_eject_info *info = (*gl)->info; uint64_t now_ms = machine_time_ms(); bool res = false; if (od_conn_eject_info_try(info, now_ms)) { res = true; od_log(&instance->logger, "shutdown", client, server, "drop client connection on graceful shutdown"); } else { od_debug( &instance->logger, "shutdown", client, server, "delay drop client connection on graceful shutdown, rate limited"); } return res; } static inline bool od_eject_conn_with_timeout(od_client_t *client, __attribute__((unused)) od_server_t *server, uint64_t timeout) { od_assert(server != NULL); if (client->time_last_active + timeout < machine_time_us()) { return true; } return false; } static od_frontend_status_t od_frontend_ctl(od_client_t *client) { if (atomic_load(&client->killed) == 1) { return OD_ECLIENT_KILLED; } return OD_OK; } static inline od_frontend_status_t od_process_drop_on_restart(od_client_t *client) { /* TODO:: drop no more than X connection per sec/min/whatever */ od_instance_t *instance = client->global->instance; od_server_t *server = client->server; int64_t shut_worker_id = od_instance_get_shutdown_worker_id(instance); if (od_likely(shut_worker_id == INVALID_COROUTINE_ID)) { /* try to optimize likely path */ return OD_OK; } if (od_unlikely(client->rule->storage->storage_type == OD_RULE_STORAGE_LOCAL)) { /* local server is not very important (db like console, pgbouncer used for stats) */ return OD_EGRACEFUL_SHUTDOWN; } if (od_unlikely(server == NULL)) { if (od_eject_conn_with_rate(client, server, instance)) { return OD_EGRACEFUL_SHUTDOWN; } return OD_OK; } if (server->state == OD_SERVER_ACTIVE /* we can drop client that are just connected and do not perform any queries */ && !od_server_synchronized(server)) { /* most probably we are not in transaction, but still executing some stmt */ return OD_OK; } if (od_unlikely(!server->is_transaction)) { if (od_eject_conn_with_rate(client, server, instance)) { return OD_EGRACEFUL_SHUTDOWN; } return OD_OK; } return OD_OK; } static inline od_frontend_status_t od_process_drop_on_idle_in_transaction(od_client_t *client, od_server_t *server) { od_instance_t *instance = client->global->instance; /* the same as above but we are going to drop client inside transaction block */ if (server != NULL && server->is_transaction && /*server is sync - that means client executed some stmts and got get result, and now just... do nothing */ od_server_synchronized(server)) { if (od_eject_conn_with_timeout( client, server, client->rule->pool->idle_in_transaction_timeout)) { od_log(&instance->logger, "shutdown", client, server, "drop idle in transaction connection on due timeout %" PRIu64 " sec", client->rule->pool->idle_in_transaction_timeout); return OD_EIDLE_IN_TRANSACTION_TIMEOUT; } } return OD_OK; } static inline od_frontend_status_t od_process_drop_transaction_pool(od_client_t *client) { if (client->server != NULL && client->rule->pool->idle_in_transaction_timeout > 0) { od_frontend_status_t status = od_process_drop_on_idle_in_transaction(client, client->server); if (status != OD_OK) { return status; } } return od_process_drop_on_restart(client); } static inline od_frontend_status_t od_process_drop_on_client_idle_timeout(od_client_t *client, od_server_t *server) { od_instance_t *instance = client->global->instance; /* * as we do not unroute client in session pooling after transaction block etc * we should consider this case separately * general logic is: if client do nothing long enough we can assume this is just a stale connection * but we need to ensure this connection was initialized etc */ if (od_unlikely( server != NULL && !server->is_transaction && /* case when we are out of any transactional block ut perform some stmt */ od_server_synchronized(server))) { if (od_eject_conn_with_timeout( client, server, client->rule->pool->client_idle_timeout)) { od_log(&instance->logger, "shutdown", client, server, "drop idle client connection on due timeout %" PRIu64 " sec", client->rule->pool->client_idle_timeout); return OD_EIDLE_TIMEOUT; } } return OD_OK; } static inline bool od_process_should_drop_session_on_pause(od_client_t *client, od_server_t *server) { if (!od_global_is_paused(client->global)) { return false; } od_route_t *route = client->route; /* do not drop console clients */ if (route->rule->storage->storage_type == OD_RULE_STORAGE_LOCAL) { return false; } if (server == NULL) { return true; } if (!od_server_synchronized(server)) { return false; } if (server->offline || !server->is_transaction) { return true; } return false; } static inline od_frontend_status_t od_process_drop_session_pool(od_client_t *client) { od_frontend_status_t status; od_server_t *server = client->server; if (od_process_should_drop_session_on_pause(client, server)) { od_instance_t *instance = client->global->instance; od_log(&instance->logger, "pause", client, server, "drop client connection on pause"); return OD_ECLIENT_READ; } if (od_unlikely(client->rule->pool->client_idle_timeout)) { status = od_process_drop_on_client_idle_timeout(client, server); if (status != OD_OK) { return status; } } if (od_unlikely(client->rule->pool->idle_in_transaction_timeout)) { status = od_process_drop_on_idle_in_transaction(client, server); if (status != OD_OK) { return status; } } status = od_process_drop_on_restart(client); return status; } static inline od_frontend_status_t od_process_connection_drop(od_client_t *client) { od_frontend_status_t status = od_frontend_ctl(client); if (status != OD_OK) { return status; } switch (client->rule->pool->pool_type) { case OD_RULE_POOL_SESSION: return od_process_drop_session_pool(client); case OD_RULE_POOL_STATEMENT: case OD_RULE_POOL_TRANSACTION: return od_process_drop_transaction_pool(client); default: abort(); } return OD_OK; } static od_frontend_status_t od_frontend_local(od_client_t *client) { od_instance_t *instance = client->global->instance; od_frontend_status_t status; for (;;) { machine_msg_t *msg = NULL; for (;;) { /* local server is always null */ od_assert(client->server == NULL); status = od_process_connection_drop(client); if (status != OD_OK) { /* Odyssey is in a state of completion, we done * the last client's request and now we can drop the connection */ return status; } /* one minute */ msg = od_read(&client->io, 60000, OD_READ_FE); if (machine_timedout()) { /* retry wait to recheck exit condition */ od_assert(msg == NULL); continue; } if (msg == NULL) { return OD_ECLIENT_READ; } else { break; } } kiwi_fe_type_t type; type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "local", client, NULL, "%s", kiwi_fe_type_to_string(type)); if (type == KIWI_FE_TERMINATE) { machine_msg_free(msg); break; } machine_msg_t *stream = machine_msg_create(0); if (stream == NULL) { machine_msg_free(msg); return OD_EOOM; } int rc; if (type == KIWI_FE_QUERY) { rc = od_console_query(client, stream, machine_msg_data(msg), machine_msg_size(msg)); machine_msg_free(msg); if (rc == -1) { machine_msg_free(stream); return OD_EOOM; } } else { /* unsupported */ machine_msg_free(msg); od_error(&instance->logger, "local", client, NULL, "unsupported request '%s'", kiwi_fe_type_to_string(type)); msg = od_frontend_errorf(client, stream, KIWI_FEATURE_NOT_SUPPORTED, "unsupported request '%s'", kiwi_fe_type_to_string(type)); if (msg == NULL) { machine_msg_free(stream); return OD_EOOM; } } /* ready */ msg = kiwi_be_write_ready(stream, 'I'); if (msg == NULL) { machine_msg_free(stream); return OD_EOOM; } rc = od_write(&client->io, stream); if (rc == -1) { return OD_ECLIENT_WRITE; } } return OD_OK; } static inline bool od_frontend_should_detach_transaction(od_server_t *server) { return !server->is_transaction; } static inline bool od_frontend_should_detach_session(od_server_t *server) { return (server->offline || od_global_is_paused(server->global)) && !server->is_transaction; } static inline bool od_frontend_should_detach(od_route_t *route, od_server_t *server) { switch (route->rule->pool->pool_type) { case OD_RULE_POOL_STATEMENT: return true; case OD_RULE_POOL_TRANSACTION: return od_frontend_should_detach_transaction(server); case OD_RULE_POOL_SESSION: return od_frontend_should_detach_session(server); default: abort(); } } static inline od_retcode_t od_frontend_log_describe(od_instance_t *instance, od_client_t *client, char *data, int size) { uint32_t name_len; char *name; int rc; kiwi_fe_describe_type_t t; rc = kiwi_be_read_describe(data, size, &name, &name_len, &t); if (rc == -1) { return NOT_OK_RESPONSE; } od_log(&instance->logger, "describe", client, client->server, "(%s) name: %.*s", t == KIWI_FE_DESCRIBE_PORTAL ? "portal" : "statement", name_len, name); return OK_RESPONSE; } static inline od_retcode_t od_frontend_log_execute(od_instance_t *instance, od_client_t *client, char *data, int size) { uint32_t name_len; char *name; int rc; rc = kiwi_be_read_execute(data, size, &name, &name_len); if (rc == -1) { return NOT_OK_RESPONSE; } od_log(&instance->logger, "execute", client, client->server, "name: %.*s", name_len, name); return OK_RESPONSE; } static inline od_retcode_t od_frontend_parse_close(char *data, int size, char **name, uint32_t *name_len, kiwi_fe_close_type_t *type) { int rc; rc = kiwi_be_read_close(data, size, name, name_len, type); if (rc == -1) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline od_retcode_t od_frontend_log_close(od_instance_t *instance, od_client_t *client, char *name, uint32_t name_len, kiwi_fe_close_type_t type) { switch (type) { case KIWI_FE_CLOSE_PORTAL: od_log(&instance->logger, "close", client, client->server, "portal, name: %.*s", name_len, name); return OK_RESPONSE; case KIWI_FE_CLOSE_PREPARED_STATEMENT: od_log(&instance->logger, "close", client, client->server, "prepared statement, name: %.*s", name_len, name); return OK_RESPONSE; default: od_log(&instance->logger, "close", client, client->server, "unknown close type, name: %.*s", name_len, name); return NOT_OK_RESPONSE; } } static inline od_retcode_t od_frontend_log_parse(od_instance_t *instance, od_client_t *client, char *context, char *data, int size) { uint32_t query_len; char *query; uint32_t name_len; char *name; int rc; rc = kiwi_be_read_parse(data, size, &name, &name_len, &query, &query_len); if (rc == -1) { return NOT_OK_RESPONSE; } od_log(&instance->logger, context, client, client->server, "%.*s %.*s", name_len, name, query_len, query); return OK_RESPONSE; } static inline od_retcode_t od_frontend_log_bind(od_instance_t *instance, od_client_t *client, char *ctx, char *data, int size) { char *portal_name; uint32_t portal_name_len; char *stmt_name; uint32_t stmt_name_len; int rc; rc = kiwi_be_read_bind_names(data, size, &portal_name, &portal_name_len, &stmt_name, &stmt_name_len); if (rc == -1) { return NOT_OK_RESPONSE; } /* * kiwi_be_read_bind_names returns lengths including the NUL */ od_log(&instance->logger, ctx, client, client->server, "bind stmt '%.*s' to portal '%.*s'", (int)(stmt_name_len - 1), stmt_name, (int)(portal_name_len - 1), portal_name); return OK_RESPONSE; } static inline int od_frontend_poll_catchup(od_client_t *client, od_route_t *route, uint32_t timeout, int64_t *lag_out) { od_instance_t *instance = client->global->instance; od_rule_storage_t *storage = route->rule->storage; od_server_t *server = client->server; int64_t lag = 0; if (server != NULL) { /* * check the replication lag only for host * that we are currently connected */ od_storage_endpoint_status_t status; od_storage_endpoint_status_get(&server->endpoint->status, &status); if (instance->config.log_debug) { char addr[256]; od_address_to_str(&server->endpoint->address, addr, sizeof(addr)); od_debug( &instance->logger, "catchup", client, NULL, "checking against %s, repl lag time sec = %" PRId64, addr, status.repl_lag_sec); } lag = status.repl_lag_sec; if (lag < 0) { lag = 0; } if (lag < (int64_t)timeout) { return OD_OK; } } else { /* * need to disallow new connections * when all hosts has too big replication lag */ for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_t *endp = &storage->endpoints[i]; od_storage_endpoint_status_t status; od_storage_endpoint_status_get(&endp->status, &status); if (instance->config.log_debug) { char addr[256]; od_address_to_str(&endp->address, addr, sizeof(addr)); od_debug( &instance->logger, "catchup", client, NULL, "checking against %s, repl lag sec = %" PRId64, addr, status.repl_lag_sec); } lag = status.repl_lag_sec; if (lag < 0) { lag = 0; } if (lag < (int64_t)timeout) { return OD_OK; } } } *lag_out = lag; od_debug(&instance->logger, "catchup", client, NULL, "client %s%.*s replication %" PRId64 " lag is over catchup timeout %d\n", client->id.id_prefix, OD_ID_LEN, client->id.id, lag, timeout); return OD_ECATCHUP_TIMEOUT; } static od_frontend_status_t od_frontend_check_replica_catchup(od_instance_t *instance, od_client_t *client) { od_route_t *route = client->route; od_assert(route); uint32_t catchup_timeout = get_effective_catchup_timeout(client); if (catchup_timeout == 0) { return OD_OK; } od_debug(&instance->logger, "catchup", client, NULL, "checking for lag before doing any actual work"); return od_frontend_poll_catchup(client, route, catchup_timeout, &client->last_catchup_lag); } static int wait_resume_transactional_step(od_client_t *client) { od_instance_t *instance = client->global->instance; od_server_t *server = client->server; /* client must be detached to start waiting for resume */ if (server != NULL) { return 0; } int rc = od_global_wait_resumed(client->global, 1000 /* 1 sec */); if (rc == 0) { od_log(&instance->logger, "pause", client, server, "waiting for global resume finished"); } else { od_log(&instance->logger, "pause", client, server, "client is waiting for global resume..."); } return rc; } static int wait_resume_step(od_client_t *client) { od_route_t *route = client->route; /* do not wait resume for console clients */ if (route->rule->storage->storage_type == OD_RULE_STORAGE_LOCAL) { return 0; } if (!od_global_is_paused(client->global)) { return 0; } switch (route->rule->pool->pool_type) { case OD_RULE_POOL_SESSION: return 0; case OD_RULE_POOL_STATEMENT: /* fall through */ case OD_RULE_POOL_TRANSACTION: return wait_resume_transactional_step(client); default: abort(); } } static od_frontend_status_t client_read_full_msg(od_client_t *client, const kiwi_header_t *header, size_t size, machine_msg_t **out, uint32_t timeout_ms) { *out = NULL; machine_msg_t *msg = client->read_msg; if (msg != NULL) { machine_msg_reset(msg); } msg = machine_msg_create_or_advance(msg, sizeof(kiwi_header_t) + size); if (msg == NULL) { return OD_EOOM; } client->read_msg = msg; char *dest; dest = machine_msg_data(msg); memcpy(dest, header, sizeof(kiwi_header_t)); dest += sizeof(kiwi_header_t); int rc = od_io_read(&client->io, dest, size, timeout_ms); if (rc == -1) { return OD_ECLIENT_READ; } od_stat_t *stats = &client->route->stats; od_stat_recv_client(stats, size + sizeof(kiwi_header_t)); *out = msg; return OD_OK; } static od_frontend_status_t client_process_message_full(od_client_t *client, machine_msg_t *msg, uint32_t timeout_ms) { od_frontend_status_t status; int rc; od_instance_t *instance = client->global->instance; od_route_t *route = client->route; od_server_t *server = client->server; machine_msg_t *xflush = NULL; od_assert(route != NULL); char *data = machine_msg_data(msg); int size = machine_msg_size(msg); kiwi_fe_type_t type = *data; if (instance->config.log_debug) { od_debug(&instance->logger, "remote client", client, server, "%s", kiwi_fe_type_to_string(type)); } switch (type) { case KIWI_FE_TERMINATE: status = OD_STOP; break; case KIWI_FE_QUERY: if (!mm_vector_empty(&client->relay.xbuf.msgs)) { xflush = kiwi_fe_write_flush(NULL); if (xflush == NULL) { status = OD_EOOM; break; } status = od_relay_process_xflush(&client->relay, xflush, timeout_ms); machine_msg_free(xflush); if (status != OD_OK) { break; } } if (instance->config.log_query || route->rule->log_query) { char *query; uint32_t query_len; rc = kiwi_be_read_query(data, size, &query, &query_len); if (rc == 0) { od_log(&instance->logger, "query", client, client->server, "%.*s", query_len, query); } else { od_error(&instance->logger, "query", client, client->server, "invalid query msg"); } } status = od_relay_process_query(&client->relay, msg, timeout_ms); break; case KIWI_FE_FUNCTION_CALL: status = od_relay_process_fcall(&client->relay, msg, timeout_ms); break; case KIWI_FE_FLUSH: if (instance->config.log_query || route->rule->log_query) { od_log(&instance->logger, "flush", client, client->server, "flush"); } status = od_relay_process_xflush(&client->relay, msg, timeout_ms); break; case KIWI_FE_SYNC: if (instance->config.log_query || route->rule->log_query) { od_log(&instance->logger, "sync", client, client->server, "sync"); } status = od_relay_process_xsync(&client->relay, msg, timeout_ms); break; case KIWI_FE_PARSE: if (instance->config.log_query || route->rule->log_query) { od_frontend_log_parse(instance, client, "parse", data, size); } status = od_relay_process_xmsg(&client->relay, msg, timeout_ms); break; case KIWI_FE_BIND: if (instance->config.log_query || route->rule->log_query) { od_frontend_log_bind(instance, client, "bind", data, size); } status = od_relay_process_xmsg(&client->relay, msg, timeout_ms); break; case KIWI_FE_DESCRIBE: if (instance->config.log_query || route->rule->log_query) { od_frontend_log_describe(instance, client, data, size); } status = od_relay_process_xmsg(&client->relay, msg, timeout_ms); break; case KIWI_FE_EXECUTE: if (instance->config.log_query || route->rule->log_query) { od_frontend_log_execute(instance, client, data, size); } status = od_relay_process_xmsg(&client->relay, msg, timeout_ms); break; case KIWI_FE_CLOSE: if (instance->config.log_query || route->rule->log_query) { char *name; uint32_t name_len; kiwi_fe_close_type_t type; rc = od_frontend_parse_close(data, size, &name, &name_len, &type); if (rc != OK_RESPONSE) { od_gerror("main", client, server, "can't parse close message"); return OD_ESERVER_READ; } od_frontend_log_close(instance, client, name, name_len, type); } status = od_relay_process_xmsg(&client->relay, msg, timeout_ms); break; case KIWI_FE_COPY_DONE: case KIWI_FE_COPY_FAIL: case KIWI_FE_COPY_DATA: /* * normally copy is initiated from some query, executed * by Query or Execute * * in first case, the Copy subprotocol messages will be * handled inside od_relay_process_query, so lets think * this is impossible situation * * in second case, we need to start copy explicitly * so lets add Flush message, * all other work will be done by relay */ if (mm_vector_empty(&client->relay.xbuf.msgs)) { /* * simply ignore this messages, see: * https://github.com/postgres/postgres/blob/REL_18_6/src/backend/tcop/postgres.c#L5013 */ status = OD_OK; break; } xflush = kiwi_fe_write_flush(NULL); if (xflush == NULL) { status = OD_EOOM; break; } /* ler the copy stream to handle this message */ od_relay_set_copy_additional(&client->relay, msg); status = od_relay_process_xflush(&client->relay, xflush, timeout_ms); od_relay_set_copy_additional(&client->relay, NULL); machine_msg_free(xflush); break; default: od_gerror("main", client, server, "unexpected message type '%c' from client", type); return OD_ECLIENT_PROTOCOL_ERROR; } return status; } static od_frontend_status_t process_possible_detach(od_client_t *client) { od_route_t *route = client->route; od_router_t *router = client->global->router; od_instance_t *instance = client->global->instance; od_server_t *server = client->server; if (server == NULL) { return OD_OK; } if (!od_server_synchronized(server)) { return OD_OK; } if (client->backend_pin) { return OD_OK; } if (!od_frontend_should_detach(route, server)) { return OD_OK; } /* should detach */ /* cleanup server */ int rc = od_reset(server); if (rc != 1) { od_debug(&instance->logger, "detach", client, server, "client %s%.*s detached from %s%.*s, server closed", client->id.id_prefix, (int)sizeof(client->id.id_prefix), client->id.id, server->id.id_prefix, (int)sizeof(server->id.id_prefix), server->id.id); od_router_close(router, client); } else { od_debug(&instance->logger, "detach", client, server, "client %s%.*s detached from %s%.*s", client->id.id_prefix, (int)sizeof(client->id.id_prefix), client->id.id, server->id.id_prefix, (int)sizeof(server->id.id_prefix), server->id.id); od_router_detach(router, client); } return OD_OK; } static od_frontend_status_t client_process_message(od_client_t *client, kiwi_header_t *header, uint32_t timeout_ms) { od_frontend_status_t status; uint32_t size; int rc = kiwi_validate_fe_header((char *)header, sizeof(kiwi_header_t), &size); if (rc == -1) { od_gerror( "main", client, client->server, "client msg header validation failed (type=%d, size=%u)", header->type, header->len); return OD_ECLIENT_READ; } size -= sizeof(uint32_t); machine_msg_t *msg = NULL; status = client_read_full_msg(client, header, size, &msg, timeout_ms); if (status != OD_OK) { return status; } status = client_process_message_full(client, msg, timeout_ms); if (client->read_msg != NULL) { machine_msg_shrink(client->read_msg, PQ_SMALL_MESSAGE_LIMIT); } if (status != OD_OK) { return status; } if (client->server != NULL && client->server->oom && client->route != NULL && client->route->rule->server_drop_on_oom) { od_gerror("main", client, client->server, "drop connection due to OOM error"); return OD_ESERVER_OOM; } status = process_possible_detach(client); return status; } static od_frontend_status_t client_read_header(od_client_t *client, kiwi_header_t *header) { while (od_readahead_unread(&client->io.readahead) < (int)sizeof(kiwi_header_t)) { int rc = od_io_read_some(&client->io, 1000); if (rc != 0) { int err = machine_errno(); if (err == ETIMEDOUT) { return OD_ECLIENT_TIMEOUT; } if (err == EAGAIN) { /* interrupted by event on server io */ od_server_t *server = client->server; od_assert(server != NULL); int ev = od_io_last_event(&server->io); if (ev & MM_R || ev & MM_ERR || ev & MM_CLOSE) { return OD_ASYNC_MSG_AVAILABLE; } continue; } return OD_ECLIENT_READ; } } int rc = od_io_read(&client->io, (char *)header, sizeof(kiwi_header_t), 1000); if (rc != 0) { /* * impossible - readahead already contains enough * bytes to read header */ return OD_ECLIENT_READ; } return OD_OK; } typedef struct { od_client_t *client; od_server_t *server; int is_client_to_server; atomic_uint_fast64_t *stop; od_frontend_status_t retstatus; int errno_; } replication_pipe_arg_t; static void od_frontend_replication_pipe(void *arg_) { replication_pipe_arg_t *arg = arg_; od_client_t *client = arg->client; od_server_t *server = arg->server; int is_client_to_server = arg->is_client_to_server; od_stat_t *stats = &client->route->stats; od_instance_t *instance = client->global->instance; od_io_t *src = NULL; od_io_t *dst = NULL; od_frontend_status_t read_err = OD_UNDEF; od_frontend_status_t write_err = OD_UNDEF; od_readahead_t *readahead = NULL; od_frontend_status_t status; int rc, errno_ = 0; if (is_client_to_server) { src = &client->io; dst = &server->io; read_err = OD_ECLIENT_READ; write_err = OD_ESERVER_WRITE; } else { src = &server->io; dst = &client->io; read_err = OD_ESERVER_READ; write_err = OD_ECLIENT_WRITE; } readahead = &src->readahead; while (1) { status = od_frontend_ctl(client); if (status != OD_OK) { break; } status = od_process_drop_on_restart(client); if (status != OD_OK) { break; } if (!od_io_connected(dst)) { if (is_client_to_server) { status = OD_ESERVER_READ; } else { status = OD_ECLIENT_READ; } break; } if (atomic_load(arg->stop) == 1) { break; } struct iovec rvec = od_readahead_read_begin(readahead); if (rvec.iov_len == 0) { rc = od_io_read_some(src, 1000); errno_ = machine_errno(); if (rc == 0) { if (is_client_to_server) { od_stat_recv_client( stats, od_readahead_unread(readahead)); } else { od_stat_recv_server( stats, od_readahead_unread(readahead)); } } else if (errno_ != EAGAIN && errno_ != ETIMEDOUT) { status = read_err; break; } continue; } size_t written; rc = od_io_write_raw(dst, rvec.iov_base, rvec.iov_len, &written, 1000, 0); errno_ = machine_errno(); od_readahead_read_commit(readahead, written); if (rc < 0 && errno_ != EAGAIN && errno_ != ETIMEDOUT) { status = write_err; break; } } arg->retstatus = status; arg->errno_ = errno_; if (is_client_to_server) { od_log(&instance->logger, "main", client, client->server, "client->server replication finished with status %d (%s), errno=%d (%s)", status, od_frontend_status_to_str(status), errno_, strerror(errno_)); } else { od_log(&instance->logger, "main", client, client->server, "server->client replication finished with status %d (%s), errno=%d (%s)", status, od_frontend_status_to_str(status), errno_, strerror(errno_)); } atomic_store(arg->stop, 1); } static od_frontend_status_t replication_impl(od_client_t *client) { od_instance_t *instance = client->global->instance; od_assert(client->server != NULL); od_server_t *server = client->server; atomic_uint_fast64_t stop; atomic_init(&stop, 0); replication_pipe_arg_t cl_srv_arg; memset(&cl_srv_arg, 0, sizeof(replication_pipe_arg_t)); cl_srv_arg.client = client; cl_srv_arg.server = client->server; cl_srv_arg.is_client_to_server = 1; cl_srv_arg.stop = &stop; replication_pipe_arg_t srv_cl_arg; memset(&srv_cl_arg, 0, sizeof(replication_pipe_arg_t)); srv_cl_arg.client = client; srv_cl_arg.server = client->server; srv_cl_arg.is_client_to_server = 0; srv_cl_arg.stop = &stop; od_io_set_peer(&client->io, &server->io); od_io_set_peer(&server->io, &client->io); char coro_name[OD_ID_LEN + 16 /* '>s' or 'id, coro_name, sizeof(coro_name)); int idlen = strlen(coro_name); memcpy(coro_name + idlen, ">s", 2); int64_t cl_srv = machine_coroutine_create_named( od_frontend_replication_pipe, &cl_srv_arg, coro_name); if (cl_srv == -1) { od_error( &instance->logger, "main", client, client->server, "can't start client->server pipe coroutine, errno=%d (%s)", machine_errno(), strerror(machine_errno())); return OD_UNDEF; } memcpy(coro_name + idlen, "logger, "main", client, client->server, "can't start server->client pipe coroutine, errno=%d (%s)", machine_errno(), strerror(machine_errno())); return OD_UNDEF; } machine_join(srv_cl); machine_join(cl_srv); od_io_remove_peer(&client->io, &server->io); od_io_remove_peer(&server->io, &client->io); /* force returning the OK status to not trigger the cleanup */ return OD_STOP; } static od_frontend_status_t od_frontend_remote_replication(od_client_t *client) { /* * we do not want to return replication backends to pool * * we do not want to have any features like catchup_timeout * to work with replication connection * * we do not want to count any statistics for them * * so lets just create two pipe coroutines that proxy * all bytes for both directions */ od_frontend_status_t status; od_instance_t *instance = client->global->instance; if (instance->config.log_session) { od_log(&instance->logger, "main", client, NULL, "replication connection"); } od_assert(client->server == NULL); if (client->server == NULL) { status = od_frontend_attach_and_deploy(client, "main"); if (status != OD_OK) { return status; } } status = replication_impl(client); /* force closing replication server connection */ od_router_close(client->global->router, client); return status; } static od_frontend_status_t process_server_async_msg(od_client_t *client, od_server_t *server) { int rc; machine_msg_t *msg = od_read(&server->io, UINT32_MAX, OD_READ_BE); if (msg == NULL) { return OD_ESERVER_READ; } char *data = machine_msg_data(msg); int size = machine_msg_size(msg); kiwi_be_type_t type = *data; od_instance_t *instance = client->global->instance; if (instance->config.log_debug) { if (type == KIWI_BE_COMMAND_COMPLETE) { const char *command_tag = data + sizeof(kiwi_header_t); od_debug(&instance->logger, "main", client, server, "%s - %s", kiwi_be_type_to_string(type), command_tag); } else { od_debug(&instance->logger, "main", client, server, "%s", kiwi_be_type_to_string(type)); } } if (type == KIWI_BE_PARAMETER_STATUS) { rc = od_backend_update_parameter(server, "main", data, size, 0); if (rc == -1) { machine_msg_free(msg); od_gerror("main", client, server, "can't update parameter"); return OD_ESERVER_READ; } } rc = od_write(&client->io, msg); if (rc != 0) { return OD_ECLIENT_READ; } return OD_OK; } static od_frontend_status_t process_server_async(od_client_t *client, od_server_t *server) { /* * https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-ASYNC */ od_frontend_status_t status = OD_OK; while (od_readahead_unread(&server->io.readahead) > 0) { status = process_server_async_msg(client, server); if (status != OD_OK) { return status; } } int rc = od_io_try_read_some(&server->io); if (rc == 0) { status = process_server_async_msg(client, server); } else { int err = machine_errno(); if (err == EWOULDBLOCK) { return OD_OK; } od_assert(err != ENOBUFS); /* disconnect or other error */ return OD_ESERVER_READ; } return status; } static od_frontend_status_t od_frontend_remote(od_client_t *client) { if (client->logical_rep || client->physical_rep) { return od_frontend_remote_replication(client); } od_instance_t *instance = client->global->instance; while (1) { od_frontend_status_t status = od_frontend_check_replica_catchup(instance, client); if (od_frontend_status_is_err(status)) { return status; } status = od_frontend_ctl(client); if (status != OD_OK) { return status; } status = od_process_connection_drop(client); if (status != OD_OK) { return status; } if (wait_resume_step(client)) { /* need to wait for resume, but also must check for conn drop */ continue; } od_server_t *server = client->server; if (server != NULL) { /* try to handle possible server's event */ status = process_server_async(client, server); if (status != OD_OK) { return status; } /* * interrupt client header reading when smth occurs on server * smth == async messages */ od_io_set_peer(&client->io, &server->io); } kiwi_header_t header; status = client_read_header(client, &header); if (server != NULL) { od_io_remove_peer(&client->io, &server->io); } if (status == OD_ASYNC_MSG_AVAILABLE) { /* * something readable happened on server * will be handled on next iteration */ continue; } if (status == OD_ECLIENT_TIMEOUT) { continue; } if (status != OD_OK) { return status; } status = client_process_message(client, &header, /* TODO */ UINT32_MAX); if (status != OD_OK) { return status; } client->time_last_active = machine_time_us(); } } static void od_frontend_report_drop(od_client_t *client, char *context, od_frontend_status_t status) { od_server_t *server = client->server; od_instance_t *instance = client->global->instance; switch (status) { case OD_EIDLE_TIMEOUT: od_log(&instance->logger, context, client, server, "client dropped by idle timeout"); od_frontend_fatal_detailed( client, KIWI_ADMIN_SHUTDOWN, "Connection was dropped by Odyssey due to idle timeout", "", "Odyssey has dropped the connection"); break; case OD_EIDLE_IN_TRANSACTION_TIMEOUT: od_log(&instance->logger, context, client, server, "client dropped by idle in transaction timeout"); od_frontend_fatal_detailed( client, KIWI_ADMIN_SHUTDOWN, "Connection was dropped by Odyssey due to idle in transaction timeout", "", "Odyssey has dropped the connection"); break; case OD_ECLIENT_KILLED: od_log(&instance->logger, context, client, server, "client killed by reload or console command"); od_frontend_fatal_detailed( client, KIWI_ADMIN_SHUTDOWN, "Connection was killed by Odyssey configuration reloading or console command", "Try to reconnect", "Odyssey has dropped the connection"); break; default: od_log(&instance->logger, context, client, server, "unexpected reason for client drop: %d (%s)", status, od_frontend_status_to_str(status)); od_frontend_fatal_detailed( client, KIWI_ADMIN_SHUTDOWN, "Connection was dropped by unexpected reason", "", "Odyssey has dropped the connection"); break; } } static void od_frontend_on_client_disconnect(od_frontend_status_t status, od_client_t *client, char *context, int force_server_close) { int rc; od_instance_t *instance = client->global->instance; od_router_t *router = client->global->router; od_server_t *server = client->server; od_route_t *route = client->route; uint64_t working_time_us = machine_time_us() - client->time_accept; if (instance->config.log_session) { if (route != NULL) { od_log(&instance->logger, context, client, server, "client disconnected addr '%s', io error = %s, status %s, route %s.%s, working time: %" PRIu64 "us", client->peer, od_io_error(&client->io), od_frontend_status_to_str(status), route->rule->db_name, route->rule->user_name, working_time_us); } else { od_log(&instance->logger, context, client, server, "client disconnected, addr '%s', io error = %s, status %s, working time %" PRIu64 "us", client->peer, od_io_error(&client->io), od_frontend_status_to_str(status), working_time_us); } } if (server == NULL) { return; } if (force_server_close) { od_router_close(router, client); return; } rc = od_reset(server); if (rc != 1) { /* close backend connection */ od_log(&instance->logger, context, client, server, "reset unsuccessful, closing server connection"); od_router_close(router, client); return; } /* push server to router server pool */ od_router_detach(router, client); } static void od_frontend_cleanup(od_client_t *client, char *context, od_frontend_status_t status, od_error_logger_t *l) { od_instance_t *instance = client->global->instance; od_router_t *router = client->global->router; od_route_t *route = client->route; od_server_t *server = client->server; char ra_desc[256]; if (od_frontend_status_is_err(status)) { od_error_logger_store_err(l, status); if (route->extra_logging_enabled && !od_route_is_dynamic(route)) { od_error_logger_store_err(route->err_logger, status); } } switch (status) { case OD_EIDLE_TIMEOUT: /* fallthrough */ case OD_EIDLE_IN_TRANSACTION_TIMEOUT: /* fallthrough */ case OD_ECLIENT_KILLED: od_frontend_report_drop(client, context, status); /* fallthrough */ case OD_STOP: /* fallthrough */ case OD_OK: od_frontend_on_client_disconnect(status, client, context, 0 /* force server close */); break; case OD_EOOM: od_error(&instance->logger, context, client, server, "%s", "memory allocation error"); if (client->server) { od_router_close(router, client); } break; case OD_ESERVER_OOM: od_frontend_fatal( client, KIWI_OUT_OF_MEMORY, "OOMed server connection was forcibly dropped"); od_frontend_on_client_disconnect(status, client, context, 1 /* force server close */); break; case OD_EATTACH: od_assert(server == NULL); od_assert(client->route != NULL); od_frontend_fatal(client, KIWI_CONNECTION_FAILURE, "failed to get remote server connection"); break; case OD_EATTACH_TOO_MANY_CONNECTIONS: od_assert(server == NULL); od_assert(client->route != NULL); od_frontend_fatal( client, KIWI_TOO_MANY_CONNECTIONS, "too many active clients for user (pool_size for " "user %s.%s reached %d)", client->startup.database.value, client->startup.user.value, client->rule != NULL ? client->rule->pool->size : -1); od_frontend_on_client_disconnect(status, client, context, 0 /* force server close */); break; case OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH: od_assert(server == NULL); od_assert(client->route != NULL); od_target_session_attrs_t attrs = od_tsa_get_effective(client); od_frontend_fatal( client, KIWI_CONNECTION_FAILURE, "can't find suitable host for tsa '%s' of user %s.%s", od_target_session_attrs_to_str(attrs), client->startup.database.value, client->startup.user.value); break; case OD_EGRACEFUL_SHUTDOWN: if (od_global_get_instance()->pid.restart_new_pid != -1) { od_frontend_fatal_detailed( client, KIWI_ADMIN_SHUTDOWN, "The Odyssey instance is performing online restart to update configuration or binary, and the connections are being drained", "Try to reconnect", "Odyssey is gracefully shutting down"); } else { od_frontend_fatal_detailed( client, KIWI_ADMIN_SHUTDOWN, "The Odyssey instance is gracefully shutting down, and the connections are being drained", "", "Odyssey is gracefully shutting down"); } /* fallthrough */ case OD_ECLIENT_READ: /*fallthrough*/ case OD_ECLIENT_WRITE: /* close client connection and reuse server * link in case of client errors */ od_frontend_on_client_disconnect(status, client, context, 0 /* force server close */); break; case OD_ECLIENT_PROTOCOL_ERROR: od_frontend_fatal( client, KIWI_PROTOCOL_VIOLATION, "unexpected message type from client or other protocol violation"); od_frontend_on_client_disconnect(status, client, context, 0 /* force server close */); break; case OD_ESERVER_CONNECT: /* server attached to client and connection failed */ if (server->error_connect && route->rule->client_fwd_error) { /* forward server error to client */ od_frontend_error_fwd(client); } else { od_frontend_fatal( client, KIWI_CONNECTION_FAILURE, "failed to connect to remote server %s%.*s", server->id.id_prefix, (int)sizeof(server->id.id), server->id.id); } od_frontend_on_client_disconnect(status, client, context, 1 /* force server close */); break; case OD_ECATCHUP_TIMEOUT: /* close client connection and close server * connection in case of server errors */ od_log(&instance->logger, context, client, server, "replication lag is too big (%" PRId64 " sec), failed to wait replica for catchup: status %s", client->last_catchup_lag, od_frontend_status_to_str(status)); od_frontend_fatal_detailed( client, KIWI_CONNECTION_FAILURE, "replication lag of the node you are trying to connect is too big, connection rejected", "wait until the replica catches up with the primary", "replication lag too big (%" PRId64 " seconds), connection rejected: %s %s", client->last_catchup_lag, client->startup.database.value, client->startup.user.value); if (client->server != NULL) { od_router_close(router, client); } break; case OD_ECLIENT_READAHEAD_FULL: { od_readahead_describe(&client->io.readahead, ra_desc, sizeof(ra_desc)); od_error(&instance->logger, context, client, server, "client readahead buffer full during write: %s", ra_desc); /* no sending FATAL to client - it's write might already stuck */ od_frontend_on_client_disconnect(status, client, context, 1 /* force server close */); break; } case OD_ESERVER_READAHEAD_FULL: { od_readahead_describe(&server->io.readahead, ra_desc, sizeof(ra_desc)); od_error(&instance->logger, context, client, server, "server readahead buffer full during write: %s, " "server %s%.*s", ra_desc, server->id.id_prefix, (int)sizeof(server->id.id), server->id.id); od_frontend_fatal( client, KIWI_CONNECTION_FAILURE, "remote server %s%.*s: readahead buffer full, " "cannot resolve write deadlock", server->id.id_prefix, (int)sizeof(server->id.id), server->id.id); od_frontend_on_client_disconnect(status, client, context, 1 /* force server close */); break; } case OD_ESERVER_READ: case OD_ESERVER_WRITE: /* close client connection and close server * connection in case of server errors */ if (server == NULL) { /* * The failure happened before a server connection * was attached (e.g. a malformed client message * processed by the relay): there is no server * to describe or to close. */ od_frontend_error(client, KIWI_CONNECTION_FAILURE, "remote server read/write error"); od_frontend_on_client_disconnect(status, client, context, 0); break; } od_log(&instance->logger, context, client, server, "server disconnected (read/write error): %s, status %s", od_io_error(&server->io), od_frontend_status_to_str(status)); od_frontend_error(client, KIWI_CONNECTION_FAILURE, "remote server read/write error %s%.*s: %s", server->id.id_prefix, (int)sizeof(server->id.id), server->id.id, od_io_error(&server->io)); od_frontend_on_client_disconnect(status, client, context, 1 /* force server close */); break; case OD_UNDEF: case OD_SKIP: case OD_ATTACH: /* fallthrough */ case OD_ESYNC_BROKEN: default: od_error(&instance->logger, context, client, server, "unexpected error status %s (%d)", od_frontend_status_to_str(status), (uint32_t)status); od_router_close(router, client); break; } } static void od_application_name_add_host(od_client_t *client) { if (client == NULL || client->io.io == NULL) { return; } char app_name_with_host[KIWI_MAX_VAR_SIZE]; char peer_name[KIWI_MAX_VAR_SIZE]; int app_name_len = 7; char *app_name = "unknown"; kiwi_var_t *app_name_var = kiwi_vars_get(&client->vars, KIWI_VAR_APPLICATION_NAME); if (app_name_var != NULL) { app_name_len = app_name_var->value_len; app_name = app_name_var->value; } od_getpeername(client->io.io, peer_name, sizeof(peer_name), 1, 1); /* include both address and port */ int length = od_snprintf(app_name_with_host, KIWI_MAX_VAR_SIZE, "%.*s - %s", app_name_len, app_name, peer_name); kiwi_vars_set(&client->vars, KIWI_VAR_APPLICATION_NAME, app_name_with_host, length + 1); /* return code ignored */ } static inline int wait_cancel_allowed(od_global_t *global, od_instance_t *instance, uint32_t timeout_ms) { if (instance->config.cancel_rate_limit != 0) { return od_rate_limiter_waitn(&global->cancel_rate_limiter, 1, timeout_ms); } else { return mm_sem_timedwait(&global->cancel_sem, timeout_ms); } } static inline void cancel_finished(od_global_t *global, od_instance_t *instance) { if (instance->config.cancel_rate_limit == 0) { mm_sem_post(&global->cancel_sem); } } void od_frontend(void *arg) { od_client_t *client = arg; od_global_t *global = client->global; od_instance_t *instance = global->instance; od_router_t *router = global->router; od_extension_t *extensions = global->extensions; od_module_t *modules = extensions->modules; od_getpeername(client->io.io, client->peer, OD_CLIENT_MAX_PEERLEN, 1, 1); /* log client connection */ if (instance->config.log_session) { od_log(&instance->logger, "startup", client, NULL, "new client connection %s", client->peer); } /* attach client io to worker machine event loop */ int rc; rc = od_io_attach(&client->io); if (rc == -1) { od_error(&instance->logger, "startup", client, NULL, "failed to transfer client io"); od_io_close(&client->io); od_client_free(client); od_routing_slot_release(global); return; } /* handle startup */ rc = od_frontend_startup(client); if (rc == -1) { od_frontend_close(client); od_routing_slot_release(global); return; } /* handle cancel request */ if (client->startup.is_cancel) { od_debug(&instance->logger, "startup", client, NULL, "cancel request"); od_routing_slot_release(global); uint32_t queue_timeout = instance->config.cancel_queue_timeout_ms >= 0 ? (uint32_t)instance->config .cancel_queue_timeout_ms : 2 * (uint32_t)instance->config.cancel_timeout_ms; rc = wait_cancel_allowed(global, instance, queue_timeout); if (rc != 0) { od_error( &instance->logger, "startup", client, NULL, "dropping cancel request due to queue timeout %u ms", queue_timeout); od_frontend_close(client); return; } od_router_cancel_t cancel; od_router_cancel_init(&cancel); rc = od_router_cancel(router, &client->startup.key, &cancel); if (rc == 0) { /* * server might be free during cancel end * so need to preserve it route ptr */ od_route_t *srv_route = cancel.server->route; od_stat_cancel(&srv_route->stats); od_cancel(client->global, cancel.storage, cancel.address, &cancel.key, &cancel.id); od_route_lock(srv_route); od_server_cancel_end(cancel.server); /* signal about possible free connection */ od_route_signal_locked(srv_route, NULL); od_route_unlock(srv_route); od_router_cancel_free(&cancel); } cancel_finished(global, instance); od_frontend_close(client); return; } /* Use client id as backend key for the client. * * This key will be used to identify a server by * user cancel requests. The key must be regenerated * for each new client-server assignment, to avoid * possibility of cancelling requests by a previous * server owners. */ client->key.key_pid = client->id.id_a; client->key.key = client->id.id_b; /* route client */ od_router_status_t router_status; router_status = od_router_route(router, client); /* routing is over */ od_routing_slot_release(global); if (od_likely(router_status == OD_ROUTER_OK)) { od_route_t *route = client->route; if (route->rule->application_name_add_host) { od_application_name_add_host(client); } /* override clients pg options if configured */ rc = kiwi_vars_override(&client->vars, &route->rule->vars); if (rc == -1) { goto cleanup; } /* set network options */ od_rule_t *rule = route->rule; if (rule->keepalive > 0) { mm_io_set_keepalive(client->io.io, 1, rule->keepalive, rule->keepalive_keep_interval, rule->keepalive_probes, rule->keepalive_usr_timeout); } if (instance->config.log_session) { od_log(&instance->logger, "startup", client, NULL, "route '%s.%s' to '%s.%s'", client->startup.database.value, client->startup.user.value, route->rule->db_name, route->rule->user_name); } } else { if (od_router_status_is_err(router_status)) { od_error_logger_store_err(router->router_err_logger, router_status); } switch (router_status) { case OD_ROUTER_ERROR: od_error(&instance->logger, "startup", client, NULL, "routing failed for '%s' client, closing", client->peer); od_frontend_error(client, KIWI_SYSTEM_ERROR, "client routing failed"); break; case OD_ROUTER_INSUFFICIENT_ACCESS: /* * disabling blind ldapsearch via odyssey error messages * to collect user account attributes */ od_error( &instance->logger, "startup", client, NULL, "route for '%s.%s' is not found by ldapsearch for '%s' client, closing", client->startup.database.value, client->startup.user.value, client->peer); od_frontend_fatal( client, KIWI_SYSTEM_ERROR, "ldap authentication failed for user \"%s\": insufficient access", client->startup.user.value); break; case OD_ROUTER_ERROR_NOT_FOUND: od_error( &instance->logger, "startup", client, NULL, "route for '%s.%s' is not found for '%s' client, closing", client->startup.database.value, client->startup.user.value, client->peer); od_frontend_error(client, KIWI_UNDEFINED_DATABASE, "route for '%s.%s' is not found", client->startup.database.value, client->startup.user.value); break; case OD_ROUTER_ERROR_LIMIT: od_error( &instance->logger, "startup", client, NULL, "global connection limit reached for '%s' client, closing", client->peer); od_frontend_error( client, KIWI_TOO_MANY_CONNECTIONS, "too many client tcp connections (global client_max)"); break; case OD_ROUTER_ERROR_LIMIT_ROUTE: od_error( &instance->logger, "startup", client, NULL, "route connection limit reached for client '%s', closing", client->peer); od_frontend_error( client, KIWI_TOO_MANY_CONNECTIONS, "too many client tcp connections (client_max for user %s.%s " "%d)", client->startup.database.value, client->startup.user.value, client->rule != NULL ? client->rule->client_max : -1); break; case OD_ROUTER_ERROR_REPLICATION: od_error( &instance->logger, "startup", client, NULL, "invalid value for parameter \"replication\" for client '%s'", client->peer); od_frontend_error( client, KIWI_CONNECTION_FAILURE, "invalid value for parameter \"replication\""); break; default: od_assert(0); break; } od_frontend_close(client); return; } /* pre-auth callback */ od_list_t *i; od_list_foreach (&modules->link, i) { od_module_t *module; module = od_container_of(i, od_module_t, link); if (module->auth_attempt_cb(client) == OD_MODULE_CB_FAIL_RETCODE) { goto cleanup; } } /* HBA check */ rc = od_hba_process(client); char client_ip[64]; od_getpeername(client->io.io, client_ip, sizeof(client_ip), 1, 0); /* client authentication */ if (rc == OK_RESPONSE) { /* Check for replication lag and reject query if too big before auth */ od_frontend_status_t catchup_status = od_frontend_check_replica_catchup(instance, client); if (od_frontend_status_is_err(catchup_status)) { od_error(&instance->logger, "catchup", client, NULL, "replication lag too big (%" PRId64 "), connection rejected: %s %s", client->last_catchup_lag, client->startup.database.value, client->startup.user.value); od_frontend_fatal_detailed( client, KIWI_CONNECTION_FAILURE, "replication lag of the node you are trying to connect is too big, connection rejected", "wait until the replica catches up with the primary", "replication lag too big (%" PRId64 " seconds), connection rejected: %s %s", client->last_catchup_lag, client->startup.database.value, client->startup.user.value); rc = NOT_OK_RESPONSE; } else { rc = od_auth_frontend(client); if (instance->config.log_session && instance->config.hba_file) { od_log(&instance->logger, "auth", client, NULL, "ip '%s' user '%s.%s': host based authentication allowed", client_ip, client->startup.database.value, client->startup.user.value); } } } else { od_error( &instance->logger, "auth", client, NULL, "ip '%s' user '%s.%s': host based authentication rejected", client_ip, client->startup.database.value, client->startup.user.value); od_frontend_error(client, KIWI_INVALID_PASSWORD, "host based authentication rejected"); } uint64_t used_memory = 0; od_rule_storage_type_t storage_type = client->route->rule->storage->storage_type; if (storage_type == OD_RULE_STORAGE_REMOTE && od_global_is_in_soft_oom(global, &used_memory)) { od_frontend_fatal(client, KIWI_OUT_OF_MEMORY, "soft out of memory"); od_error(&instance->logger, "startup", client, NULL, "drop connection due to soft oom (usage is %" PRIu64 " KB)", used_memory / 1024); rc = NOT_OK_RESPONSE; } if (rc != OK_RESPONSE) { /* rc == -1 * here we ignore module retcode because auth already failed * we just inform side modules that usr was trying to log in */ od_list_foreach (&modules->link, i) { od_module_t *module; module = od_container_of(i, od_module_t, link); module->auth_complete_cb(client, rc); } goto cleanup; } /* auth result callback */ od_list_foreach (&modules->link, i) { od_module_t *module; module = od_container_of(i, od_module_t, link); rc = module->auth_complete_cb(client, rc); if (rc != OD_MODULE_CB_OK_RETCODE) { /* user blocked from module callback */ goto cleanup; } } /* setup client and run main loop */ od_route_t *route = client->route; od_frontend_status_t status; status = OD_UNDEF; switch (route->rule->storage->storage_type) { case OD_RULE_STORAGE_LOCAL: { status = od_frontend_local_setup(client); if (status != OD_OK) { break; } status = od_frontend_local(client); break; } case OD_RULE_STORAGE_REMOTE: { status = od_frontend_setup(client); if (status != OD_OK) { break; } status = od_frontend_remote(client); break; } } od_error_logger_t *l; l = router->route_pool.err_logger; od_frontend_cleanup(client, "main", status, l); od_list_foreach (&modules->link, i) { od_module_t *module; module = od_container_of(i, od_module_t, link); module->disconnect_cb(client, status); } /* cleanup */ cleanup: /* detach client from its route */ od_router_unroute(router, client); /* close frontend connection */ od_frontend_close(client); } odyssey-1.6.0-rc1/sources/global.c000066400000000000000000000046631526064202200167770ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include od_global_t *current_global od_read_mostly = NULL; int od_global_init(od_global_t *global, od_instance_t *instance, od_system_t *system, od_router_t *router, od_cron_t *cron, od_worker_pool_t *worker_pool, od_extension_t *extensions, od_hba_t *hba) { global->instance = instance; global->system = system; global->router = router; global->cron = cron; global->worker_pool = worker_pool; global->extensions = extensions; global->hba = hba; atomic_init(&global->pause, 0ULL); global->resume_waiters = mm_wait_list_create(NULL); if (global->resume_waiters == NULL) { return 1; } memset(&global->soft_oom, 0, sizeof(global->soft_oom)); memset(&global->host_watcher, 0, sizeof(global->host_watcher)); return 0; } od_global_t *od_global_create(od_instance_t *instance, od_system_t *system, od_router_t *router, od_cron_t *cron, od_worker_pool_t *worker_pool, od_extension_t *extensions, od_hba_t *hba) { od_global_t *g = od_malloc(sizeof(od_global_t)); if (g == NULL) { return NULL; } if (od_global_init(g, instance, system, router, cron, worker_pool, extensions, hba)) { od_free(g); return NULL; } od_global_set(g); return g; } void od_global_set(od_global_t *global) { current_global = global; } od_global_t *od_global_get(void) { return current_global; } od_logger_t *od_global_get_logger(void) { od_global_t *global = od_global_get(); return &global->instance->logger; } od_instance_t *od_global_get_instance(void) { od_global_t *global = od_global_get(); return global->instance; } int od_global_is_in_soft_oom(od_global_t *global, uint64_t *used_memory) { od_config_t *config = &global->instance->config; if (!config->soft_oom.enabled) { return 0; } return od_soft_oom_is_in_soft_oom(&global->soft_oom, used_memory); } void od_global_read_host_utilization(od_global_t *global, float *cpu, float *mem) { od_config_t *config = &global->instance->config; if (!config->host_watcher_enabled) { *cpu = 0.0; *mem = 0.0; return; } od_host_watcher_read(&global->host_watcher, cpu, mem); } int od_routing_slot_acquire(od_global_t *global, uint32_t timeout_ms) { return mm_sem_timedwait(&global->routing_sem, timeout_ms); } void od_routing_slot_release(od_global_t *global) { mm_sem_post(&global->routing_sem); } odyssey-1.6.0-rc1/sources/group.c000066400000000000000000000034021526064202200166610ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include int od_group_free(od_group_t *group) { if (group == NULL) { return NOT_OK_RESPONSE; } if (group->route_usr) { od_free(group->route_usr); } if (group->route_db) { od_free(group->route_db); } if (group->group_name) { od_free(group->group_name); } if (group->group_query) { od_free(group->group_query); } if (group->group_query_user) { od_free(group->group_query_user); } if (group->group_query_db) { od_free(group->group_query_db); } od_free(group); return OK_RESPONSE; } int od_group_parse_val_datarow(machine_msg_t *msg, char **group_member) { char *pos = (char *)machine_msg_data(msg) + 1; uint32_t pos_size = machine_msg_size(msg) - 1; /* size */ uint32_t size; int rc; rc = kiwi_read32(&size, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } /* count */ uint16_t count; rc = kiwi_read16(&count, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } if (count != 1) { goto error; } /* (not used) */ uint32_t val_len; rc = kiwi_read32(&val_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { goto error; } *group_member = od_strndup(pos, val_len); if (*group_member == NULL) { goto error; } return OK_RESPONSE; error: return NOT_OK_RESPONSE; } od_group_member_name_item_t *od_group_member_name_item_add(od_list_t *members) { od_group_member_name_item_t *item = (od_group_member_name_item_t *)od_malloc( sizeof(od_group_member_name_item_t)); if (item == NULL) { return NULL; } memset(item, 0, sizeof(*item)); od_list_init(&item->link); od_list_append(members, &item->link); return item; } odyssey-1.6.0-rc1/sources/hba.c000066400000000000000000000127021526064202200162620ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include void od_hba_init(od_hba_t *hba) { mm_mutex_init(&hba->lock); od_hba_rules_init(&hba->rules); } void od_hba_free(od_hba_t *hba) { od_hba_rules_free(&hba->rules); mm_mutex_destroy(&hba->lock); } void od_hba_lock(od_hba_t *hba) { mm_mutex_lock2(&hba->lock); } void od_hba_unlock(od_hba_t *hba) { mm_mutex_unlock(&hba->lock); } void od_hba_reload(od_hba_t *hba, od_hba_rules_t *rules) { od_hba_lock(hba); od_hba_rules_free(&hba->rules); od_list_init(&hba->rules); od_list_t *i, *n; od_list_foreach_safe (rules, i, n) { od_hba_rule_t *rule; rule = od_container_of(i, od_hba_rule_t, link); od_hba_rules_add(&hba->rules, rule); } od_hba_unlock(hba); } bool od_hba_validate_name(char *client_name, od_hba_rule_name_t *name, char *client_other_name) { if (name->flags & OD_HBA_NAME_ALL) { return true; } if ((name->flags & OD_HBA_NAME_SAMEUSER) && strcmp(client_name, client_other_name) == 0) { return true; } od_list_t *i; od_hba_rule_name_item_t *item; od_list_foreach (&name->values, i) { item = od_container_of(i, od_hba_rule_name_item_t, link); if (item->value != NULL && strcmp(client_name, item->value) == 0) { return true; } } return false; } void od_hba_rules_print(od_hba_t *hba, od_logger_t *logger) { static const char *conn_type_str[] = { [OD_CONFIG_HBA_LOCAL] = "local", [OD_CONFIG_HBA_HOST] = "host", [OD_CONFIG_HBA_HOSTSSL] = "hostssl", [OD_CONFIG_HBA_HOSTNOSSL] = "hostnossl", }; od_list_t *i; od_list_foreach (&hba->rules, i) { od_hba_rule_t *rule = od_container_of(i, od_hba_rule_t, link); /* database names */ char db_buf[256]; int db_pos = 0; if (rule->database.flags & OD_HBA_NAME_ALL) { db_pos += snprintf(db_buf + db_pos, sizeof(db_buf) - db_pos, "all"); } else if (rule->database.flags & OD_HBA_NAME_SAMEUSER) { db_pos += snprintf(db_buf + db_pos, sizeof(db_buf) - db_pos, "sameuser"); } else { od_list_t *j; od_list_foreach (&rule->database.values, j) { od_hba_rule_name_item_t *item = od_container_of( j, od_hba_rule_name_item_t, link); if (db_pos > 0 && db_buf[db_pos - 1] != '(') { db_pos += snprintf( db_buf + db_pos, sizeof(db_buf) - db_pos, ","); } db_pos += snprintf(db_buf + db_pos, sizeof(db_buf) - db_pos, "%s", item->value); } } if (db_pos == 0) { snprintf(db_buf, sizeof(db_buf), "(none)"); } /* user names */ char user_buf[256]; int user_pos = 0; if (rule->user.flags & OD_HBA_NAME_ALL) { user_pos += snprintf(user_buf + user_pos, sizeof(user_buf) - user_pos, "all"); } else if (rule->user.flags & OD_HBA_NAME_SAMEUSER) { user_pos += snprintf(user_buf + user_pos, sizeof(user_buf) - user_pos, "sameuser"); } else { od_list_t *j; od_list_foreach (&rule->user.values, j) { od_hba_rule_name_item_t *item = od_container_of( j, od_hba_rule_name_item_t, link); if (user_pos > 0 && user_buf[user_pos - 1] != '(') { user_pos += snprintf( user_buf + user_pos, sizeof(user_buf) - user_pos, ","); } user_pos += snprintf(user_buf + user_pos, sizeof(user_buf) - user_pos, "%s", item->value); } } if (user_pos == 0) { snprintf(user_buf, sizeof(user_buf), "(none)"); } const char *addr_str = (rule->connection_type == OD_CONFIG_HBA_LOCAL) ? "" : (rule->address_range.string_value != NULL) ? rule->address_range.string_value : ""; od_log(logger, "config", NULL, NULL, "hba rule %-10s db=%-20s user=%-20s addr=%-20s %s", conn_type_str[rule->connection_type], db_buf, user_buf, addr_str, rule->auth_method == OD_CONFIG_HBA_ALLOW ? "allow" : "deny"); } } int od_hba_process(od_client_t *client) { od_instance_t *instance = client->global->instance; od_hba_t *hba = client->global->hba; od_list_t *i; od_hba_rule_t *rule; od_hba_rules_t *rules; if (instance->config.hba_file == NULL) { return OK_RESPONSE; } struct sockaddr_storage sa; int salen = sizeof(sa); struct sockaddr *saddr = (struct sockaddr *)&sa; int rc = machine_getpeername(client->io.io, saddr, &salen); if (rc == -1) { return -1; } od_hba_lock(hba); rules = &hba->rules; od_hba_unlock(hba); od_list_foreach (rules, i) { rule = od_container_of(i, od_hba_rule_t, link); if (sa.ss_family == AF_UNIX) { if (rule->connection_type != OD_CONFIG_HBA_LOCAL) { continue; } } else if (rule->connection_type == OD_CONFIG_HBA_LOCAL) { continue; } else if (rule->connection_type == OD_CONFIG_HBA_HOSTSSL && !client->startup.is_ssl_request) { continue; } else if (rule->connection_type == OD_CONFIG_HBA_HOSTNOSSL && client->startup.is_ssl_request) { continue; } else if (sa.ss_family == AF_INET || sa.ss_family == AF_INET6) { if (!od_address_validate(&rule->address_range, &sa)) { continue; } } if (!od_hba_validate_name(client->startup.database.value, &rule->database, client->startup.user.value)) { continue; } if (!od_hba_validate_name(client->startup.user.value, &rule->user, client->startup.database.value)) { continue; } rc = rule->auth_method == OD_CONFIG_HBA_ALLOW ? OK_RESPONSE : NOT_OK_RESPONSE; return rc; } return NOT_OK_RESPONSE; } odyssey-1.6.0-rc1/sources/hba_rule.c000066400000000000000000000033111526064202200173050ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include od_hba_rule_name_item_t *od_hba_rule_name_item_add(od_hba_rule_name_t *name) { od_hba_rule_name_item_t *item = (od_hba_rule_name_item_t *)od_malloc( sizeof(od_hba_rule_name_item_t)); if (item == NULL) { return NULL; } memset(item, 0, sizeof(*item)); od_list_init(&item->link); od_list_append(&name->values, &item->link); return item; } od_hba_rule_t *od_hba_rule_create(void) { od_hba_rule_t *hba = (od_hba_rule_t *)od_malloc(sizeof(od_hba_rule_t)); if (hba == NULL) { return NULL; } memset(hba, 0, sizeof(*hba)); od_list_init(&hba->database.values); od_list_init(&hba->user.values); hba->address_range = od_address_range_create_default(); return hba; } void od_hba_rule_free(od_hba_rule_t *hba) { od_list_t *i, *n; od_hba_rule_name_item_t *item; od_list_foreach_safe (&hba->database.values, i, n) { item = od_container_of(i, od_hba_rule_name_item_t, link); od_free(item->value); od_free(item); } od_list_foreach_safe (&hba->user.values, i, n) { item = od_container_of(i, od_hba_rule_name_item_t, link); od_free(item->value); od_free(item); } od_address_range_destroy(&hba->address_range); od_free(hba); } void od_hba_rules_init(od_hba_rules_t *rules) { od_list_init(rules); } void od_hba_rules_free(od_hba_rules_t *rules) { od_list_t *i, *n; od_list_foreach_safe (rules, i, n) { od_hba_rule_t *hba; hba = od_container_of(i, od_hba_rule_t, link); od_hba_rule_free(hba); } } void od_hba_rules_add(od_hba_rules_t *rules, od_hba_rule_t *rule) { od_list_init(&rule->link); od_list_append(rules, &rule->link); } odyssey-1.6.0-rc1/sources/host_watcher.c000066400000000000000000000117561526064202200202320ustar00rootroot00000000000000#include #include #include #include #include #define PROC_MEMINFO_PATH "/proc/meminfo" #define PROC_STAT_PATH "/proc/stat" DEFINE_SIMPLE_MODULE_LOGGER(hw, "host-watcher") static inline uint64_t sub(uint64_t a, uint64_t b) { return a > b ? a - b : 0; } static inline void hw_update_cpu_stat(od_host_watcher_t *hw, const od_cpu_stat_t *stat) { hw->cpu_stat.idle_diff = sub(stat->idle, hw->cpu_stat.idle); hw->cpu_stat.total_diff = sub(stat->total, hw->cpu_stat.total); hw->cpu_stat.idle = stat->idle; hw->cpu_stat.total = stat->total; } static inline void hw_update_mem_stat(od_host_watcher_t *hw, const od_mem_stat_t *stat) { memcpy(&hw->mem_stat, stat, sizeof(od_mem_stat_t)); } static inline void hw_update(od_host_watcher_t *hw, const od_cpu_stat_t *cpu, const od_mem_stat_t *mem) { mm_spinlock_lock(&hw->lock); hw_update_cpu_stat(hw, cpu); hw_update_mem_stat(hw, mem); mm_spinlock_unlock(&hw->lock); } static inline int get_cpu_stat(od_cpu_stat_t *out) { /* https://www.kernel.org/doc/html/latest/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat */ FILE *fp = fopen(PROC_STAT_PATH, "re"); if (!fp) { hw_error("can't open '%s': %s", PROC_STAT_PATH, strerror(errno)); return -1; } static OD_THREAD_LOCAL char cpu_stat_buffer[4096 + 1]; if (fgets(cpu_stat_buffer, sizeof(cpu_stat_buffer), fp) == NULL) { hw_error("can't read '%s': %s", PROC_STAT_PATH, strerror(errno)); return -1; } fclose(fp); uint64_t usertime, nicetime, systemtime, idletime; uint64_t io_wait = 0, irq = 0, soft_irq = 0, steal = 0, guest = 0, guestnice = 0; sscanf(cpu_stat_buffer, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &usertime, &nicetime, &systemtime, &idletime, &io_wait, &irq, &soft_irq, &steal, &guest, &guestnice); usertime -= guest; nicetime -= guestnice; uint64_t idle_time = idletime + io_wait; uint64_t system_time = systemtime + irq + soft_irq; uint64_t vm_time = guest + guestnice; uint64_t total_time = usertime + nicetime + system_time + idle_time + steal + vm_time; out->total = total_time; out->idle = idle_time; return 0; } static inline int get_memory_stat(od_mem_stat_t *out) { /* https://www.kernel.org/doc/html/latest/filesystems/proc.html#meminfo */ FILE *fp = fopen(PROC_MEMINFO_PATH, "re"); if (!fp) { hw_error("can't open '%s': %s", PROC_MEMINFO_PATH, strerror(errno)); return -1; } char line[256]; int64_t mem_available = 0, mem_total = 0; while (fgets(line, sizeof(line), fp)) { if (sscanf(line, "MemAvailable: %" PRId64 " kB", &mem_available) == 1) { continue; } if (sscanf(line, "MemTotal: %" PRId64 " kB", &mem_total) == 1) { continue; } } if (ferror(fp)) { hw_error("can't read '%s': %s", PROC_MEMINFO_PATH, strerror(errno)); fclose(fp); return -1; } fclose(fp); out->total = mem_total; out->available = mem_available; return 0; } static inline void hw_watcher(void *arg) { od_host_watcher_t *hw = arg; hw_log("host watcher started"); while (1) { int rc = machine_wait_flag_wait(hw->stop_flag, 1000); if (rc != -1 && machine_errno() != ETIMEDOUT) { hw_log("stop flag is set, exiting host watcher"); break; } od_mem_stat_t mem; if (get_memory_stat(&mem)) { continue; } od_cpu_stat_t cpu; if (get_cpu_stat(&cpu)) { continue; } hw_update(hw, &cpu, &mem); float ucpu, umem; od_host_watcher_read(hw, &ucpu, &umem); hw_log("update values to: %.2f%% cpu and %.2f%% mem", ucpu, umem); } hw_log("host watcher finished"); } int od_host_watcher_init(od_host_watcher_t *hw) { memset(&hw->cpu_stat, 0, sizeof(od_cpu_stat_t)); memset(&hw->mem_stat, 0, sizeof(od_mem_stat_t)); mm_spinlock_init(&hw->lock); hw->hz = sysconf(_SC_CLK_TCK); if (hw->hz == -1) { return -1; } hw->stop_flag = machine_wait_flag_create(); if (hw->stop_flag == NULL) { mm_spinlock_destroy(&hw->lock); return -1; } hw->worker_id = machine_create("host-watcher", hw_watcher, hw); if (hw->worker_id == -1) { hw_error("can't start host watcher machine"); machine_wait_flag_destroy(hw->stop_flag); mm_spinlock_destroy(&hw->lock); return -1; } return 0; } void od_host_watcher_destroy(od_host_watcher_t *hw) { machine_wait_flag_set(hw->stop_flag); machine_wait(hw->worker_id); machine_wait_flag_destroy(hw->stop_flag); mm_spinlock_destroy(&hw->lock); } static inline float get_mem_util(const od_host_watcher_t *hw) { return (float)(hw->mem_stat.total - hw->mem_stat.available) / hw->mem_stat.total * 100.0; } static inline float get_cpu_util(const od_host_watcher_t *hw) { return (float)(hw->cpu_stat.total_diff - hw->cpu_stat.idle_diff) / hw->cpu_stat.total_diff * 100.0; } void od_host_watcher_read(od_host_watcher_t *hw, float *cpu, float *mem) { mm_spinlock_lock(&hw->lock); *cpu = get_cpu_util(hw); *mem = get_mem_util(hw); mm_spinlock_unlock(&hw->lock); } odyssey-1.6.0-rc1/sources/include/000077500000000000000000000000001526064202200170055ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/address.h000066400000000000000000000035071526064202200206100ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef enum { OD_ADDRESS_TYPE_UNIX, OD_ADDRESS_TYPE_TCP } od_address_type_t; static inline const char *od_address_type_str(od_address_type_t type) { switch (type) { case OD_ADDRESS_TYPE_TCP: return "tcp"; case OD_ADDRESS_TYPE_UNIX: return "unix"; default: abort(); } } struct od_address { char *host; /* NULL - terminated */ int port; od_address_type_t type; char availability_zone[OD_MAX_AVAILABILITY_ZONE_LENGTH]; }; void od_address_init(od_address_t *addr); void od_address_move(od_address_t *dst, od_address_t *src); int od_address_copy(od_address_t *dst, const od_address_t *src); void od_address_destroy(od_address_t *addr); int od_address_cmp(const od_address_t *a, const od_address_t *b); int od_parse_addresses(const char *host_str, od_address_t **out, size_t *count); void od_address_to_str(const od_address_t *addr, char *out, size_t max); int od_address_is_localhost(const od_address_t *addr); typedef struct od_address_range od_address_range_t; struct od_address_range { char *string_value; int string_value_len; struct sockaddr_storage addr; struct sockaddr_storage mask; int is_hostname; int is_default; }; od_address_range_t od_address_range_create_default(void); void od_address_range_destroy(od_address_range_t *range); int od_address_range_copy(const od_address_range_t *, od_address_range_t *); int od_address_range_read_prefix(od_address_range_t *, char *); int od_address_read(struct sockaddr_storage *, const char *); bool od_address_equals(struct sockaddr *, struct sockaddr *); bool od_address_range_equals(const od_address_range_t *, const od_address_range_t *); bool od_address_validate(const od_address_range_t *, struct sockaddr_storage *); odyssey-1.6.0-rc1/sources/include/affinity.h000066400000000000000000000046141526064202200207740ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #if defined(__linux__) #include #endif #include #if defined(__linux__) #define OD_AFFINITY_MAX_CPUS CPU_SETSIZE #else #define OD_AFFINITY_MAX_CPUS 256 #endif #ifndef OD_AFFINITY_MAX_RULES #define OD_AFFINITY_MAX_RULES 128 #endif typedef enum { OD_AFFINITY_MODE_OFF = 0, OD_AFFINITY_MODE_AUTO, OD_AFFINITY_MODE_RULES } od_affinity_mode_t; typedef struct { uint64_t bits[ROUNDUPDIV(OD_AFFINITY_MAX_CPUS, 8 * sizeof(uint64_t))]; } od_affinity_cpuset_t; void od_affinity_cpuset_init(od_affinity_cpuset_t *set); int od_affinity_cpuset_add(od_affinity_cpuset_t *set, int i); int od_affinity_cpuset_get(const od_affinity_cpuset_t *set, int i); int od_affinity_cpuset_count(const od_affinity_cpuset_t *set); #if defined(__linux__) /* * convert internal mask representation to cpu_set_t suitable for * pthread_setaffinity_np() / sched_setaffinity(). */ void od_affinity_cpuset_export(const od_affinity_cpuset_t *set, cpu_set_t *cpuset); #endif void od_affinity_cpuset_to_str(const od_affinity_cpuset_t *set, char *out, size_t max); /* * parse a cpuset string: * "0" * "0,1,2" * "0-3" * "0-3,8-11" */ int od_affinity_cpuset_parse(const char *str, od_affinity_cpuset_t *mask, char *errbuf, size_t errbuf_len); typedef enum { OD_AFFINITY_ROLE_NONE = 0, OD_AFFINITY_ROLE_WORKER, OD_AFFINITY_ROLE_HANDSHAKE, } od_affinity_role_t; typedef struct { od_affinity_cpuset_t cpuset; od_affinity_role_t role; /* -1 if worker/handshake id is not specified */ int index; } od_affinity_rule_t; void od_affinity_rule_init(od_affinity_rule_t *rule); /* * parse a rule string: * "worker:0" * "handshake[1]:2" * "handshake:0-3" * "worker:0-3,8-11" */ int od_affinity_rule_parse(const char *str, od_affinity_rule_t *rule, char *errbuf, size_t errbuf_size); typedef struct { od_affinity_mode_t mode; od_affinity_rule_t rules[OD_AFFINITY_MAX_RULES]; size_t nrules; } od_affinity_config_t; void od_affinity_config_init(od_affinity_config_t *config); int od_affinity_config_parse(const char *str, size_t len, od_affinity_config_t *config, char *errbuf, size_t errbuf_len); od_affinity_mode_t od_affinity_resolve(const od_affinity_config_t *config, od_affinity_rule_t *out, od_affinity_role_t role, int idx); odyssey-1.6.0-rc1/sources/include/alloc/000077500000000000000000000000001526064202200200775ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/alloc/linear.h000066400000000000000000000023021526064202200215170ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include typedef enum { OD_LINEAR_ALLOC_ZERO = 1 << 0, OD_LINEAR_ALLOC_REALLOC = 1 << 1, } od_linear_alloc_flags_t; struct od_linear_alloc { uint8_t *buf; size_t size; size_t used; int track_sizes; }; static inline void od_linear_alloc_destroy(od_linear_alloc_t *al) { (void)al; } static inline void od_linear_alloc_free(od_linear_alloc_t *al, void *ptr) { (void)al; (void)ptr; } /* * must be called before every usage of the arena, it drops all previous * allocations and sets the arena mode */ void od_linear_alloc_init(od_linear_alloc_t *al, uint8_t *buf, size_t size); void od_linear_alloc_reset(od_linear_alloc_t *al, int flags); void *od_linear_alloc_alloc(od_linear_alloc_t *al, size_t size, int flags); void *od_linear_alloc_calloc(od_linear_alloc_t *al, size_t nmemb, size_t size); void *od_linear_alloc_realloc(od_linear_alloc_t *al, void *ptr, size_t new_size); char *od_linear_alloc_strdup(od_linear_alloc_t *al, const char *s); char *od_linear_alloc_strndup(od_linear_alloc_t *al, const char *s, size_t n); odyssey-1.6.0-rc1/sources/include/attach.h000066400000000000000000000006121526064202200204210ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include od_frontend_status_t od_attach_extended_endpoint(od_instance_t *instance, char *context, od_router_t *router, od_client_t *client, od_storage_endpoint_t *endpoint); int od_attach_extended(od_instance_t *instance, char *context, od_router_t *router, od_client_t *client); odyssey-1.6.0-rc1/sources/include/attribute.h000066400000000000000000000004571526064202200211670ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ int read_attribute_buf(char **data, size_t *data_size, char attr_key, char **out, size_t *out_size); int read_any_attribute_buf(char **data, size_t *data_size, char *attribute_ptr, char **out, size_t *out_size); odyssey-1.6.0-rc1/sources/include/auth.h000066400000000000000000000003141526064202200201150ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int od_auth_frontend(od_client_t *); int od_auth_backend(od_server_t *, machine_msg_t *, od_client_t *); odyssey-1.6.0-rc1/sources/include/auth_query.h000066400000000000000000000002771526064202200213520ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #define ODYSSEY_AUTH_QUERY_MAX_PASSWORD_LEN 4096 int od_auth_query(od_client_t *, char *); odyssey-1.6.0-rc1/sources/include/backend.h000066400000000000000000000044021526064202200205450ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include int od_backend_connect(od_server_t *, char *, kiwi_params_t *, od_client_t *, od_rule_storage_t *); typedef enum { OD_TSA_CHECK_OK, OD_TSA_CHECK_FAIL, OD_TSA_CHECK_BACKEND_ERROR, } od_tsa_check_result_t; od_tsa_check_result_t od_backend_check_tsa(od_rule_storage_t *, od_storage_endpoint_t *, char *, od_server_t *, od_client_t *, od_target_session_attrs_t); int od_backend_connect_cancel(od_server_t *, od_rule_storage_t *, const od_address_t *, kiwi_key_t *); typedef enum { /* obey tls_mode, i.e. plaintext for the first attempt of "allow" */ OD_BACKEND_TLS_DEFAULT, /* negotiate TLS even if tls_mode alone would have chosen plaintext */ OD_BACKEND_TLS_NEGOTIATE, } od_backend_tls_attempt_t; /* need perform startup after this */ int od_backend_connect_to(od_server_t *, char *, const od_address_t *, od_tls_opts_t *, od_backend_tls_attempt_t); int od_backend_startup(od_server_t *server, kiwi_params_t *route_params, od_client_t *client); /* * startup preallocated connection * for connections that was created for min_pool_size */ int od_backend_startup_preallocated(od_server_t *server, kiwi_params_t *route_params, od_client_t *client); void od_backend_close_connection(od_server_t *); void od_backend_close(od_server_t *); void od_backend_error(od_server_t *, char *, char *, uint32_t); int od_backend_update_parameter(od_server_t *, char *, char *, uint32_t, int); int od_backend_ready(od_server_t *, char *, uint32_t); int od_backend_ready_wait(od_server_t *, char *, uint32_t); od_retcode_t od_backend_query_send(od_server_t *server, char *context, const char *query, char *param, int len, uint32_t timeout_ms); od_retcode_t od_backend_query(od_server_t *, char *, char *, char *, int, uint32_t); int od_backend_not_connected(od_server_t *); int od_backend_need_startup(od_server_t *); int od_backend_update_endpoint_status(od_instance_t *instance, od_client_t *client, od_server_t *server, char *context, const char *lag_query, od_storage_endpoint_t *endpoint); odyssey-1.6.0-rc1/sources/include/balancing.h000066400000000000000000000037451526064202200211050ustar00rootroot00000000000000#pragma once #include #include #include #include typedef enum { OD_BALANCING_METHOD_UNDEF, OD_BALANCING_METHOD_ROUNDROBIN, OD_BALANCING_METHOD_LEASTCONN, OD_BALANCING_METHOD_WEIGHTED, OD_BALANCING_METHOD_WEIGHTED_LEASTCONN, OD_BALANCING_METHOD_RESPONSETIME, } od_balancing_method_t; od_balancing_method_t od_balancing_method_from_str(const char *str, size_t len); const char *od_balancing_method_to_str(od_balancing_method_t method); typedef struct { /* * high 32 bit is counter for local endpoints * low 32 bit is counter for global endpoints */ atomic_uint_fast64_t counter; } od_method_roundroubin_t; typedef struct { /* TODO: do not use char* here */ char *host; double weight; } od_balancing_host_weight_t; typedef struct { size_t nweights; od_balancing_host_weight_t *weights; } od_method_weighted_t; typedef struct { size_t nweights; od_balancing_host_weight_t *weights; } od_method_weighted_leastconn_t; typedef struct { double time_weight; double conn_weight; } od_method_responsetime_t; typedef struct { struct { od_balancing_method_t type; int az_aware; od_method_roundroubin_t roundrobin; od_method_weighted_t weighted; od_method_weighted_leastconn_t weighted_leastconn; od_method_responsetime_t responsetime; } method; int debug_notice; } od_storage_balancing_t; void od_storage_balancing_init(od_storage_balancing_t *b); void od_storage_balancing_destroy(od_storage_balancing_t *b); typedef int (*od_balancing_filter_fn)(od_storage_endpoint_t *endp, void *arg); void od_storage_balancing_copy(od_storage_balancing_t *dest, const od_storage_balancing_t *src); size_t od_storage_balancing_select(od_storage_balancing_t *b, od_rule_storage_t *storage, od_route_t *route, od_storage_endpoint_t **out, size_t max, od_balancing_filter_fn filter, void *arg); od_storage_balancing_t *od_balancing_get_effective(od_client_t *client, od_rule_storage_t *storage); odyssey-1.6.0-rc1/sources/include/build.h.cmake000066400000000000000000000015421526064202200213360ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* AUTO-GENERATED (see version.h.cmake) */ #cmakedefine OD_DEVEL_LVL @OD_DEVEL_LVL@ #cmakedefine PAM_FOUND @PAM_FOUND@ #cmakedefine LDAP_FOUND @LDAP_FOUND@ #cmakedefine PROM_FOUND @PROM_FOUND@ #cmakedefine PROMHTTP_FOUND @PROMHTTP_FOUND@ #cmakedefine USE_TCMALLOC_PROFILE #define ODYSSEY_NAME "@ODYSSEY_NAME@" #define ODYSSEY_VERSION_NUMBER "@ODYSSEY_VERSION_NUMBER@" #define ODYSSEY_VERSION_FULL "@ODYSSEY_VERSION_FULL@" #cmakedefine ODYSSEY_VERSION_GIT "@ODYSSEY_VERSION_GIT@" #define ODYSSEY_COMPILER_STRING "@ODYSSEY_COMPILER_STRING@" #define ODYSSEY_COMPILER_NAME "@ODYSSEY_COMPILER_NAME@" #define ODYSSEY_COMPILER_VERSION "@ODYSSEY_COMPILER_VERSION@" #define ODYSSEY_BUILD_ARCH "@BUILD_BITS@" #define ODYSSEY_BUILD_TYPE "@ODYSSEY_BUILD_TYPE@" odyssey-1.6.0-rc1/sources/include/cancel.h000066400000000000000000000004101526064202200203760ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include int od_cancel(od_global_t *, od_rule_storage_t *, const od_address_t *, kiwi_key_t *, od_id_t *); odyssey-1.6.0-rc1/sources/include/cfg/000077500000000000000000000000001526064202200175445ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/cfg/convert.h000066400000000000000000000005221526064202200213740ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include int od_cfg_convert_model(const od_cfg_model_t *model, od_config_t *config, od_rules_t *rules, od_global_t *global, od_hba_rules_t *hba_rules, od_cfg_diag_list_t *diags); odyssey-1.6.0-rc1/sources/include/cfg/ctx.h000066400000000000000000000026501526064202200205160ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef struct od_cfg_parse_ctx { const char *filename; char *input; size_t input_size; int lexer_line; int lexer_column; size_t lexer_offset; od_cfg_model_t *model; od_cfg_diag_list_t *diags; int include_depth; int allow_include; /* * bison scopes */ od_cfg_listen_t *current_listen; od_cfg_shared_pool_t *current_shared_pool; od_cfg_conn_drop_options_t *current_conn_drop_options; od_cfg_storage_t *current_storage; od_cfg_balancing_t *current_balancing; od_cfg_balancing_method_t *current_balancing_method; od_cfg_soft_oom_t *current_soft_oom; od_cfg_soft_oom_drop_t *current_soft_oom_drop; od_cfg_ldap_endpoint_t *current_ldap_endpoint; od_cfg_database_t *current_database; od_cfg_route_t *current_user; od_cfg_route_t *current_watchdog; od_cfg_route_t *current_group; od_cfg_string_kvp_list_t *current_kvp_list; od_cfg_ldap_storage_credentials_t *current_ldap_creds; od_cfg_query_parsing_t *current_query_parsing; /* * last identifier that the lexer couldn't match to any keyword */ char *last_unknown_ident; } od_cfg_parse_ctx_t; void od_cfg_parse_ctx_init(od_cfg_parse_ctx_t *ctx, const char *filename, char *input, size_t input_size, od_cfg_model_t *model, od_cfg_diag_list_t *diags); void od_cfg_parse_ctx_free(od_cfg_parse_ctx_t *ctx); odyssey-1.6.0-rc1/sources/include/cfg/diag.h000066400000000000000000000024371526064202200206270ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include typedef enum { OD_CFG_DIAG_ERROR, OD_CFG_DIAG_WARNING, } od_cfg_diag_level_t; typedef struct { od_cfg_diag_level_t level; od_cfg_location_t location; char *message; char *hint; } od_cfg_diag_t; typedef struct { od_cfg_diag_t *items; size_t count; size_t capacity; } od_cfg_diag_list_t; void od_cfg_diag_list_init(od_cfg_diag_list_t *diags); void od_cfg_diag_list_free(od_cfg_diag_list_t *diags); void od_cfg_diag_dumpf(FILE *file, const od_cfg_diag_list_t *diags); void od_cfg_diag_error(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *fmt, ...) __attribute__((format(printf, 3, 4))); void od_cfg_diag_error_with_hint(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *hint, const char *fmt, ...) __attribute__((format(printf, 4, 5))); void od_cfg_diag_warning(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *fmt, ...) __attribute__((format(printf, 3, 4))); void od_cfg_diag_deprecated_ignored(od_cfg_diag_list_t *diags, od_cfg_location_t location, const char *option_name); int od_cfg_diag_has_errors(const od_cfg_diag_list_t *diags); odyssey-1.6.0-rc1/sources/include/cfg/keywords.h000066400000000000000000000003671526064202200215720ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int od_cfg_keyword_lookup(const char *str, size_t len); const char *od_cfg_keyword_suggest(const char *str, size_t len, int max_distance); odyssey-1.6.0-rc1/sources/include/cfg/loc.h000066400000000000000000000015351526064202200204760ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include typedef struct { const char *filename; int first_line; int first_column; int last_line; int last_column; size_t first_offset; size_t last_offset; } od_cfg_location_t; static inline od_cfg_location_t od_cfg_location_empty(const char *filename) { od_cfg_location_t loc; loc.filename = filename; loc.first_line = 1; loc.first_column = 1; loc.last_line = 1; loc.last_column = 1; loc.first_offset = 0; loc.last_offset = 0; return loc; } static inline od_cfg_location_t od_cfg_location_copy(od_cfg_location_t loc) { loc.filename = loc.filename ? od_strdup(loc.filename) : NULL; return loc; } static inline void od_cfg_location_free(od_cfg_location_t *loc) { od_free((char *)loc->filename); loc->filename = NULL; } odyssey-1.6.0-rc1/sources/include/cfg/model.h000066400000000000000000000357131526064202200210260ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include struct od_cfg_parse_ctx; typedef struct { int is_set; od_cfg_location_t location; } od_cfg_seen_t; void od_cfg_seen_set(od_cfg_seen_t *seen, od_cfg_location_t location); void od_cfg_seen_reset(od_cfg_seen_t *seen); void od_cfg_seen_free(od_cfg_seen_t *seen); typedef struct { int value; od_cfg_seen_t seen; } od_cfg_bool_field_t; typedef struct { int value; od_cfg_seen_t seen; } od_cfg_int_field_t; typedef struct { uint64_t value; od_cfg_seen_t seen; } od_cfg_u64_field_t; typedef struct { char *value; od_cfg_seen_t seen; } od_cfg_string_field_t; typedef struct od_cfg_global { od_cfg_bool_field_t daemonize; od_cfg_bool_field_t sequential_routing; od_cfg_bool_field_t enable_online_restart; od_cfg_bool_field_t virtual_processing; od_cfg_bool_field_t virtual_transaction; od_cfg_bool_field_t bindwith_reuseport; od_cfg_bool_field_t enable_host_watcher; od_cfg_bool_field_t log_debug; od_cfg_bool_field_t log_to_stdout; od_cfg_bool_field_t log_config; od_cfg_bool_field_t log_session; od_cfg_bool_field_t log_query; od_cfg_bool_field_t log_stats; od_cfg_bool_field_t log_async; od_cfg_bool_field_t log_syslog; od_cfg_bool_field_t smart_search_path_enquoting; od_cfg_bool_field_t nodelay; od_cfg_bool_field_t disable_nolinger; od_cfg_bool_field_t log_general_stats_prom; od_cfg_bool_field_t log_route_stats_prom; od_cfg_string_field_t pid_file; od_cfg_string_field_t unix_socket_dir; od_cfg_string_field_t unix_socket_mode; od_cfg_string_field_t locks_dir; od_cfg_string_field_t external_auth_socket_path; od_cfg_string_field_t availability_zone; od_cfg_string_field_t log_file; od_cfg_string_field_t log_format; od_cfg_string_field_t log_syslog_ident; od_cfg_string_field_t log_syslog_facility; od_cfg_string_field_t cpu_affinity; od_cfg_string_field_t hba_file; od_cfg_int_field_t priority; od_cfg_int_field_t graceful_shutdown_timeout_ms; od_cfg_int_field_t log_queue_depth; od_cfg_int_field_t stats_interval; od_cfg_int_field_t client_max; od_cfg_int_field_t client_max_routing; od_cfg_int_field_t accept_rate_limit; od_cfg_int_field_t server_login_retry; od_cfg_int_field_t readahead; od_cfg_int_field_t keepalive; od_cfg_int_field_t keepalive_keep_interval; od_cfg_int_field_t keepalive_probes; od_cfg_int_field_t keepalive_usr_timeout; od_cfg_int_field_t max_sigterms_to_die; od_cfg_int_field_t backend_connect_timeout_ms; od_cfg_int_field_t cancel_timeout_ms; od_cfg_int_field_t cancel_queue_timeout_ms; od_cfg_int_field_t cancel_max_inflight; od_cfg_int_field_t cancel_rate_limit; od_cfg_int_field_t resolvers; od_cfg_int_field_t dns_cache_ttl; od_cfg_int_field_t cache_msg_gc_size; od_cfg_int_field_t cache_msg_gc_count; od_cfg_int_field_t cache_coroutine; od_cfg_int_field_t coroutine_stack_size; od_cfg_int_field_t system_coroutine_stack_size; od_cfg_int_field_t promhttp_server_port; od_cfg_int_field_t group_checker_interval; od_cfg_int_field_t workers; } od_cfg_global_t; typedef struct od_cfg_listen_storage { char *name; od_cfg_location_t location; } od_cfg_listen_storage_t; typedef struct od_cfg_balancing_method { od_cfg_seen_t seen; od_cfg_location_t location; char *name; od_cfg_bool_field_t az_aware; } od_cfg_balancing_method_t; typedef struct od_cfg_balancing { od_cfg_seen_t seen; od_cfg_location_t location; od_cfg_balancing_method_t method; od_cfg_bool_field_t show_notice_messages; } od_cfg_balancing_t; typedef struct od_cfg_listen { od_cfg_location_t location; od_cfg_string_field_t host; od_cfg_int_field_t port; od_cfg_string_field_t target_session_attrs; od_cfg_balancing_t balancing; od_cfg_int_field_t client_login_timeout; od_cfg_int_field_t backlog; od_cfg_string_field_t tls; od_cfg_string_field_t tls_ca_file; od_cfg_string_field_t tls_key_file; od_cfg_string_field_t tls_cert_file; od_cfg_string_field_t tls_protocols; od_cfg_int_field_t catchup_timeout; od_cfg_int_field_t reserved_clients; od_cfg_listen_storage_t **storages; size_t storages_count; size_t storages_capacity; } od_cfg_listen_t; od_cfg_listen_storage_t *od_cfg_listen_add_storage(od_cfg_listen_t *listen, od_cfg_location_t location, const char *name); typedef struct od_cfg_route od_cfg_route_t; typedef struct od_cfg_storage { od_cfg_location_t location; char *name; od_cfg_location_t name_location; od_cfg_string_field_t type; od_cfg_string_field_t host; od_cfg_int_field_t port; od_cfg_string_field_t tls; od_cfg_string_field_t tls_ca_file; od_cfg_string_field_t tls_key_file; od_cfg_string_field_t tls_cert_file; od_cfg_string_field_t tls_protocols; od_cfg_int_field_t server_max_routing; od_cfg_int_field_t endpoints_status_poll_interval_ms; od_cfg_balancing_t balancing; od_cfg_route_t *watchdog; } od_cfg_storage_t; od_cfg_route_t *od_cfg_storage_add_watchdog(od_cfg_storage_t *s, od_cfg_location_t loc); typedef struct od_cfg_conn_drop_options { od_cfg_seen_t seen; od_cfg_location_t location; od_cfg_bool_field_t drop_enabled; od_cfg_int_field_t rate; od_cfg_int_field_t interval_ms; } od_cfg_conn_drop_options_t; typedef struct od_cfg_shared_pool { od_cfg_location_t location; char *name; od_cfg_location_t name_location; od_cfg_int_field_t pool_size; } od_cfg_shared_pool_t; typedef struct od_cfg_soft_oom_drop { od_cfg_seen_t seen; od_cfg_location_t location; od_cfg_string_field_t signal; od_cfg_int_field_t max_rate; } od_cfg_soft_oom_drop_t; typedef struct od_cfg_soft_oom { od_cfg_seen_t seen; od_cfg_location_t location; od_cfg_u64_field_t limit; od_cfg_string_field_t process; od_cfg_int_field_t check_interval_ms; od_cfg_soft_oom_drop_t drop; } od_cfg_soft_oom_t; typedef enum { OD_CFG_QUERY_PARSING_MODE_DISABLED, OD_CFG_QUERY_PARSING_MODE_MINIMAL, OD_CFG_QUERY_PARSING_MODE_FULL } od_cfg_query_parsing_mode_type_t; typedef struct od_cfg_query_parsing_mode { od_cfg_seen_t seen; od_cfg_query_parsing_mode_type_t value; } od_cfg_query_parsing_mode_field_t; typedef struct od_cfg_query_parsing { od_cfg_seen_t seen; od_cfg_query_parsing_mode_field_t mode; od_cfg_u64_field_t mem_limit; } od_cfg_query_parsing_t; typedef struct od_cfg_ldap_endpoint { od_cfg_location_t location; char *name; od_cfg_location_t name_location; od_cfg_string_field_t ldapserver; od_cfg_u64_field_t ldapport; od_cfg_string_field_t ldapprefix; od_cfg_string_field_t ldapsuffix; od_cfg_string_field_t ldapsearchattribute; od_cfg_string_field_t ldapscope; od_cfg_string_field_t ldapscheme; od_cfg_string_field_t ldapbasedn; od_cfg_string_field_t ldapbinddn; od_cfg_string_field_t ldapbindpasswd; od_cfg_string_field_t ldapsearchfilter; } od_cfg_ldap_endpoint_t; typedef enum od_cfg_conn_type { OD_CFG_CONN_TYPE_DEFAULT, OD_CFG_CONN_TYPE_LOCAL, OD_CFG_CONN_TYPE_HOST, OD_CFG_CONN_TYPE_HOSTSSL, OD_CFG_CONN_TYPE_HOSTNOSSL } od_cfg_conn_type_t; typedef struct od_cfg_rule_auth_common_name { od_cfg_location_t location; char *name; od_cfg_location_t name_location; int is_default; } od_cfg_rule_auth_common_name_t; typedef struct od_cfg_ldap_storage_credentials { od_cfg_location_t location; char *name; od_cfg_location_t name_location; od_cfg_string_field_t ldap_storage_username; od_cfg_string_field_t ldap_storage_password; } od_cfg_ldap_storage_credentials_t; typedef struct { od_cfg_location_t location; char *key; od_cfg_location_t key_location; char *value; od_cfg_location_t value_location; } od_cfg_string_kvp_t; typedef struct { od_cfg_location_t location; od_cfg_seen_t seen; od_cfg_string_kvp_t **pairs; size_t pairs_count; size_t pairs_capacity; } od_cfg_string_kvp_list_t; od_cfg_string_kvp_t * od_cfg_string_kvp_list_add(od_cfg_string_kvp_list_t *list, od_cfg_location_t location, char *key, od_cfg_location_t key_location, char *value, od_cfg_location_t value_location); struct od_cfg_route { od_cfg_location_t location; int is_default; char *name; od_cfg_location_t name_location; char *address_range; od_cfg_location_t address_range_location; od_cfg_conn_type_t conn_type; od_cfg_location_t conn_type_location; od_cfg_rule_auth_common_name_t **auth_common_names; size_t auth_common_names_count; size_t auth_common_names_capacity; od_cfg_string_field_t authentication; od_cfg_string_field_t auth_module; od_cfg_bool_field_t enable_mdb_iamproxy_auth; od_cfg_string_field_t mdb_iamproxy_socket_path; od_cfg_string_field_t auth_pam_service; od_cfg_string_field_t target_session_attrs; od_cfg_string_field_t auth_query; od_cfg_string_field_t auth_query_db; od_cfg_string_field_t auth_query_user; od_cfg_bool_field_t password_passthrough; od_cfg_string_field_t password; od_cfg_string_field_t role; od_cfg_int_field_t client_max; od_cfg_bool_field_t client_fwd_error; od_cfg_bool_field_t client_show_id; od_cfg_bool_field_t reserve_session_server_connection; od_cfg_string_field_t quantiles; od_cfg_bool_field_t application_name_add_host; od_cfg_bool_field_t server_drop_on_backend_plan_error; od_cfg_bool_field_t server_drop_on_oom; od_cfg_u64_field_t server_lifetime; od_cfg_int_field_t ldap_pool_size; od_cfg_int_field_t ldap_pool_timeout; od_cfg_int_field_t ldap_pool_ttl; od_cfg_bool_field_t maintain_params; od_cfg_string_field_t pool; od_cfg_string_field_t pool_routing; od_cfg_int_field_t min_pool_size; od_cfg_int_field_t pool_size; od_cfg_int_field_t pool_timeout; od_cfg_int_field_t pool_ttl; od_cfg_bool_field_t pool_discard; od_cfg_bool_field_t pool_smart_discard; od_cfg_string_field_t pool_discard_query; od_cfg_bool_field_t pool_cancel; od_cfg_bool_field_t pool_rollback; od_cfg_u64_field_t pool_reset_timeout_ms; od_cfg_bool_field_t pool_reserve_prepared_statement; od_cfg_int_field_t server_pstmt_cache_size; od_cfg_bool_field_t pool_pin_on_listen; od_cfg_bool_field_t pool_attach_check; od_cfg_bool_field_t pool_acquire_fail_fast; od_cfg_u64_field_t pool_notice_after_waiting_ms; od_cfg_u64_field_t pool_client_idle_timeout; od_cfg_u64_field_t pool_idle_in_transaction_timeout; od_cfg_string_field_t shared_pool; od_cfg_string_field_t storage; od_cfg_string_field_t storage_database; od_cfg_string_field_t storage_user; od_cfg_string_field_t storage_password; od_cfg_bool_field_t log_debug; od_cfg_bool_field_t log_query; od_cfg_string_field_t ldap_endpoint_name; od_cfg_string_field_t ldap_storage_credentials_attr; od_cfg_ldap_storage_credentials_t **ldap_storage_credentials; size_t ldap_storage_credentials_count; size_t ldap_storage_credentials_capacity; od_cfg_string_field_t watchdog_lag_query; od_cfg_int_field_t watchdog_lag_interval; od_cfg_int_field_t catchup_timeout; od_cfg_string_kvp_list_t pgoptions; od_cfg_string_kvp_list_t backend_startup_options; od_cfg_string_field_t group_query; od_cfg_string_field_t group_query_user; od_cfg_string_field_t group_query_db; od_cfg_int_field_t keepalive; od_cfg_int_field_t keepalive_keep_interval; od_cfg_int_field_t keepalive_probes; od_cfg_int_field_t keepalive_usr_timeout; }; od_cfg_ldap_storage_credentials_t * od_cfg_user_route_add_ldap_creds(od_cfg_route_t *r, od_cfg_location_t location, char *name); od_cfg_rule_auth_common_name_t *od_cfg_user_route_add_auth_common_name( od_cfg_route_t *r, char *name, int is_default, od_cfg_location_t location, od_cfg_location_t name_location); typedef struct od_cfg_database { od_cfg_location_t location; int is_default; char *name; od_cfg_location_t name_location; od_cfg_route_t **users; size_t users_count; size_t users_capacity; od_cfg_route_t **groups; size_t groups_count; size_t groups_capacity; } od_cfg_database_t; od_cfg_route_t *od_cfg_database_add_user( od_cfg_database_t *db, char *name, int is_default, od_cfg_location_t name_location, od_cfg_location_t location, char *addr_range, od_cfg_location_t addr_range_location, od_cfg_conn_type_t conn_type, od_cfg_location_t conn_type_location); od_cfg_route_t *od_cfg_database_add_group(od_cfg_database_t *db, char *name, od_cfg_location_t name_location, od_cfg_location_t location, char *addr_range, od_cfg_location_t addr_range_location, od_cfg_conn_type_t conn_type, od_cfg_location_t conn_type_location); typedef struct { od_cfg_location_t location; od_cfg_global_t global; od_cfg_conn_drop_options_t conn_drop_options; od_cfg_soft_oom_t soft_oom; od_cfg_query_parsing_t query_parsing; od_cfg_listen_t **listens; size_t listens_count; size_t listens_capacity; od_cfg_shared_pool_t **shared_pools; size_t shared_pools_count; size_t shared_pools_capacity; od_cfg_storage_t **storages; size_t storages_count; size_t storages_capacity; od_cfg_ldap_endpoint_t **ldap_endpoints; size_t ldap_endpoints_count; size_t ldap_endpoints_capacity; od_cfg_database_t **databases; size_t databases_count; size_t databases_capacity; } od_cfg_model_t; void od_cfg_model_init(od_cfg_model_t *model); void od_cfg_model_free(od_cfg_model_t *model); void od_cfg_global_free(od_cfg_global_t *g); void od_cfg_model_dumpf(FILE *file, const od_cfg_model_t *model); int od_cfg_validate_model(od_cfg_model_t *model, od_cfg_diag_list_t *diags); od_cfg_listen_t *od_cfg_model_add_listen(od_cfg_model_t *model, od_cfg_location_t location); od_cfg_storage_t *od_cfg_model_add_storage(od_cfg_model_t *model, char *name, od_cfg_location_t name_location, od_cfg_location_t location); od_cfg_ldap_endpoint_t * od_cfg_model_add_ldap_endpoint(od_cfg_model_t *model, char *name, od_cfg_location_t name_location, od_cfg_location_t location); od_cfg_shared_pool_t * od_cfg_model_add_shared_pool(od_cfg_model_t *model, char *name, od_cfg_location_t name_location, od_cfg_location_t location); od_cfg_database_t *od_cfg_model_add_database(od_cfg_model_t *model, char *name, int is_default, od_cfg_location_t name_location, od_cfg_location_t location); int od_cfg_set_bool(od_cfg_diag_list_t *diags, od_cfg_bool_field_t *field, int value, od_cfg_location_t location, const char *field_name); int od_cfg_set_int(od_cfg_diag_list_t *diags, od_cfg_int_field_t *field, int value, od_cfg_location_t location, const char *field_name); int od_cfg_set_int_from_i64(od_cfg_diag_list_t *diags, od_cfg_int_field_t *field, int64_t value, od_cfg_location_t location, const char *field_name); int od_cfg_set_int_range_from_i64(od_cfg_diag_list_t *diags, od_cfg_int_field_t *field, int64_t value, int min, int max, od_cfg_location_t location, const char *field_name); int od_cfg_set_u64(od_cfg_diag_list_t *diags, od_cfg_u64_field_t *field, uint64_t value, od_cfg_location_t location, const char *field_name); int od_cfg_set_string(od_cfg_diag_list_t *diags, od_cfg_string_field_t *field, char *value, od_cfg_location_t location, const char *field_name); int od_cfg_set_query_parsing_mode(od_cfg_diag_list_t *diags, od_cfg_query_parsing_mode_field_t *field, od_cfg_query_parsing_mode_type_t value, od_cfg_location_t location, const char *field_name); odyssey-1.6.0-rc1/sources/include/cfg/reader.h000066400000000000000000000007021526064202200211560ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_cfg_parse_file(const char *path, od_cfg_model_t *model, od_cfg_diag_list_t *diags); /* internal: parse with explicit include depth; used from parse.y include action */ int od_cfg_parse_file_depth(const char *path, od_cfg_model_t *model, od_cfg_diag_list_t *diags, int depth, int allow_include); odyssey-1.6.0-rc1/sources/include/cfg_import.h000066400000000000000000000005211526064202200213050ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include int od_cfg_import(od_logger_t *logger, od_config_t *config, od_rules_t *rules, od_global_t *global, od_hba_rules_t *hba_rules, const char *config_file); odyssey-1.6.0-rc1/sources/include/client.h000066400000000000000000000066461526064202200204500ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { OD_CLIENT_UNDEF, OD_CLIENT_PENDING, OD_CLIENT_ACTIVE, OD_CLIENT_QUEUE } od_client_state_t; #define OD_CLIENT_MAX_PEERLEN 128 struct od_client { od_client_state_t state; od_pool_client_type_t type; od_id_t id; uint64_t coroutine_id; machine_tls_t *tls; od_io_t io; od_relay_t relay; od_rule_t *rule; od_system_server_t *source; uint64_t time_accept; uint64_t time_setup; uint64_t time_last_active; /* set on entering OD_CLIENT_QUEUE, valid only in that state */ uint64_t time_queue_start; bool is_watchdog; kiwi_be_startup_t startup; kiwi_vars_t vars; kiwi_key_t key; /* do not set this field directly, use od_server_attach_client */ od_server_t *server; od_route_t *route; char peer[OD_CLIENT_MAX_PEERLEN]; /* desc preparet statements ids */ mm_hashmap_t *prep_stmt_ids; /* portal name -> *od_pstmt_t (extended protocol portals tracking) */ mm_hashmap_t *portals; /* passwd from config rule */ kiwi_password_t password; /* user - proveded passwd, fallback to use this when no other option is available*/ kiwi_password_t received_password; od_global_t *global; od_list_t link_pool; od_list_t link; /* Used to kill client in kill_client or odyssey reload */ atomic_uint_fast64_t killed; /* client-side GUC state. XXX: maybe use embedded struct here */ bool backend_pin; bool opportunistic_acquire; bool physical_rep; bool logical_rep; /* storage_user & storage_password provided by ldapsearch result */ #ifdef LDAP_FOUND char *ldap_storage_username; int ldap_storage_username_len; char *ldap_storage_password; int ldap_storage_password_len; char *ldap_auth_dn; #endif int64_t last_catchup_lag; /* external_id for logging additional info about client authneticated with external auth */ char *external_id; int pending_begin; uint8_t scram_client_key[OD_SCRAM_MAX_KEY_LEN]; uint8_t scram_server_key[OD_SCRAM_MAX_KEY_LEN]; int scram_key_valid; machine_msg_t *read_msg; od_query_ctx_t query_ctx; }; static inline od_retcode_t od_client_init_hm(od_client_t *client) { client->prep_stmt_ids = od_client_pstmt_hashmap_create(); if (client->prep_stmt_ids == NULL) { return NOT_OK_RESPONSE; } client->portals = od_client_portal_hashmap_create(); if (client->portals == NULL) { od_client_pstmt_hashmap_free(client->prep_stmt_ids); client->prep_stmt_ids = NULL; return NOT_OK_RESPONSE; } return OK_RESPONSE; } void od_client_init(od_client_t *client); static inline od_client_t *od_client_allocate(void) { od_client_t *client = od_malloc(sizeof(od_client_t)); if (client == NULL) { return NULL; } od_client_init(client); return client; } void od_client_free(od_client_t *client); /* * for service clients (auth, watchdog, etc) usage * * adds od_io_close which is performed in od_frontend_close and * must be performed for service clients too */ static inline void od_client_free_extended(od_client_t *client) { od_io_close(&client->io); od_client_free(client); } static inline void od_client_kill(od_client_t *client) { atomic_store(&client->killed, 1UL); } uint32_t od_client_login_timeout(const od_client_t *client); odyssey-1.6.0-rc1/sources/include/client_pool.h000066400000000000000000000064451526064202200214760ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef int (*od_client_pool_cb_t)(od_client_t *, void **); struct od_client_pool { od_list_t active; od_list_t queue; od_list_t pending; int count_active; int count_queue; int count_pending; }; static inline void od_client_pool_init(od_client_pool_t *pool) { pool->count_active = 0; pool->count_queue = 0; pool->count_pending = 0; od_list_init(&pool->active); od_list_init(&pool->queue); od_list_init(&pool->pending); } static inline void od_client_pool_set(od_client_pool_t *pool, od_client_t *client, od_client_state_t state) { if (client->state == state) { return; } switch (client->state) { case OD_CLIENT_UNDEF: break; case OD_CLIENT_ACTIVE: pool->count_active--; break; case OD_CLIENT_QUEUE: pool->count_queue--; break; case OD_CLIENT_PENDING: pool->count_pending--; break; } od_list_t *target = NULL; switch (state) { case OD_CLIENT_UNDEF: break; case OD_CLIENT_ACTIVE: target = &pool->active; pool->count_active++; break; case OD_CLIENT_QUEUE: target = &pool->queue; pool->count_queue++; break; case OD_CLIENT_PENDING: target = &pool->pending; pool->count_pending++; break; } od_list_unlink(&client->link_pool); od_list_init(&client->link_pool); if (target) { od_list_append(target, &client->link_pool); } client->state = state; } static inline od_client_t *od_client_pool_next(od_client_pool_t *pool, od_client_state_t state) { int target_count = 0; od_list_t *target = NULL; switch (state) { case OD_CLIENT_ACTIVE: target = &pool->active; target_count = pool->count_active; break; case OD_CLIENT_QUEUE: target = &pool->queue; target_count = pool->count_queue; break; case OD_CLIENT_PENDING: target = &pool->pending; target_count = pool->count_pending; break; case OD_CLIENT_UNDEF: od_assert(0); break; } if (target_count == 0) { return NULL; } od_client_t *client; client = od_container_of(target->next, od_client_t, link_pool); return client; } static inline od_client_t *od_client_pool_foreach(od_client_pool_t *pool, od_client_state_t state, od_client_pool_cb_t callback, void **argv) { od_list_t *target = NULL; switch (state) { case OD_CLIENT_ACTIVE: target = &pool->active; break; case OD_CLIENT_QUEUE: target = &pool->queue; break; case OD_CLIENT_PENDING: target = &pool->pending; break; case OD_CLIENT_UNDEF: od_assert(0); break; } od_client_t *client; od_list_t *i, *n; od_list_foreach_safe (target, i, n) { client = od_container_of(i, od_client_t, link_pool); int rc; rc = callback(client, argv); if (rc) { return client; } } return NULL; } static inline int od_client_pool_total(od_client_pool_t *pool) { return pool->count_active + pool->count_queue + pool->count_pending; } /* age of the longest-queued client, 0 if none; queue head is the oldest */ static inline uint64_t od_client_pool_max_queue_time_us(od_client_pool_t *pool, uint64_t now_us) { if (pool->count_queue == 0) { return 0; } od_client_t *oldest = od_container_of(pool->queue.next, od_client_t, link_pool); if (now_us <= oldest->time_queue_start) { return 0; } return now_us - oldest->time_queue_start; } odyssey-1.6.0-rc1/sources/include/common/000077500000000000000000000000001526064202200202755ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/common/base64.h000066400000000000000000000010551526064202200215330ustar00rootroot00000000000000/* * base64.h * Encoding and decoding routines for base64 without whitespace * support. * * Portions Copyright (c) 2001-2025, PostgreSQL Global Development Group * * src/include/common/base64.h */ #ifndef BASE64_H #define BASE64_H /* base 64 */ pg_nodiscard extern int pg_b64_encode(const uint8 *src, int len, char *dst, int dstlen); pg_nodiscard extern int pg_b64_decode(const char *src, int len, uint8 *dst, int dstlen); extern int pg_b64_enc_len(int srclen); extern int pg_b64_dec_len(int srclen); #endif /* BASE64_H */ odyssey-1.6.0-rc1/sources/include/common/cryptohash.h000066400000000000000000000022771526064202200226420ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * cryptohash.h * Generic headers for cryptographic hash functions. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/include/common/cryptohash.h * *------------------------------------------------------------------------- */ #ifndef PG_CRYPTOHASH_H #define PG_CRYPTOHASH_H /* Context Structures for each hash function */ typedef enum { PG_MD5 = 0, PG_SHA1, PG_SHA224, PG_SHA256, PG_SHA384, PG_SHA512, } pg_cryptohash_type; /* opaque context, private to each cryptohash implementation */ typedef struct pg_cryptohash_ctx pg_cryptohash_ctx; extern pg_cryptohash_ctx *pg_cryptohash_create(pg_cryptohash_type type); extern int pg_cryptohash_init(pg_cryptohash_ctx *ctx); extern int pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len); extern int pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len); extern void pg_cryptohash_free(pg_cryptohash_ctx *ctx); extern const char *pg_cryptohash_error(pg_cryptohash_ctx *ctx); #endif /* PG_CRYPTOHASH_H */ odyssey-1.6.0-rc1/sources/include/common/fe_memutils.h000066400000000000000000000061541526064202200227650ustar00rootroot00000000000000/* * fe_memutils.h * memory management support for frontend code * * Copyright (c) 2003-2025, PostgreSQL Global Development Group * * src/include/common/fe_memutils.h */ #ifndef FE_MEMUTILS_H #define FE_MEMUTILS_H /* * Assumed maximum size for allocation requests. * * We don't enforce this, so the actual maximum is the platform's SIZE_MAX. * But it's useful to have it defined in frontend builds, so that common * code can check for oversized requests without having frontend-vs-backend * differences. Also, some code relies on MaxAllocSize being no more than * INT_MAX/2, so rather than setting this to SIZE_MAX, make it the same as * the backend's value. */ #define MaxAllocSize ((Size)0x3fffffff) /* 1 gigabyte - 1 */ /* * Flags for pg_malloc_extended and palloc_extended, deliberately named * the same as the backend flags. */ #define MCXT_ALLOC_HUGE \ 0x01 /* allow huge allocation (> 1 GB) not * actually used for frontends */ #define MCXT_ALLOC_NO_OOM 0x02 /* no failure if out-of-memory */ #define MCXT_ALLOC_ZERO 0x04 /* zero allocated memory */ /* * "Safe" memory allocation functions --- these exit(1) on failure * (except pg_malloc_extended with MCXT_ALLOC_NO_OOM) */ extern char *pg_strdup(const char *in); extern void *pg_malloc(size_t size); extern void *pg_malloc0(size_t size); extern void *pg_malloc_extended(size_t size, int flags); extern void *pg_realloc(void *ptr, size_t size); extern void pg_free(void *ptr); /* * Variants with easier notation and more type safety */ /* * Allocate space for one object of type "type" */ #define pg_malloc_object(type) ((type *)pg_malloc(sizeof(type))) #define pg_malloc0_object(type) ((type *)pg_malloc0(sizeof(type))) /* * Allocate space for "count" objects of type "type" */ #define pg_malloc_array(type, count) ((type *)pg_malloc(sizeof(type) * (count))) #define pg_malloc0_array(type, count) \ ((type *)pg_malloc0(sizeof(type) * (count))) /* * Change size of allocation pointed to by "pointer" to have space for "count" * objects of type "type" */ #define pg_realloc_array(pointer, type, count) \ ((type *)pg_realloc(pointer, sizeof(type) * (count))) /* Equivalent functions, deliberately named the same as backend functions */ extern char *pstrdup(const char *in); extern char *pnstrdup(const char *in, Size size); extern void *palloc(Size size); extern void *palloc0(Size size); extern void *palloc_extended(Size size, int flags); extern void *repalloc(void *pointer, Size size); extern void pfree(void *pointer); #define palloc_object(type) ((type *)palloc(sizeof(type))) #define palloc0_object(type) ((type *)palloc0(sizeof(type))) #define palloc_array(type, count) ((type *)palloc(sizeof(type) * (count))) #define palloc0_array(type, count) ((type *)palloc0(sizeof(type) * (count))) #define repalloc_array(pointer, type, count) \ ((type *)repalloc(pointer, sizeof(type) * (count))) /* sprintf into a palloc'd buffer --- these are in psprintf.c */ extern char *psprintf(const char *fmt, ...) pg_attribute_printf(1, 2); extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0); #endif /* FE_MEMUTILS_H */ odyssey-1.6.0-rc1/sources/include/common/hmac.h000066400000000000000000000017131526064202200213600ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * hmac.h * Generic headers for HMAC * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/include/common/hmac.h * *------------------------------------------------------------------------- */ #ifndef PG_HMAC_H #define PG_HMAC_H #include "common/cryptohash.h" /* opaque context, private to each HMAC implementation */ typedef struct pg_hmac_ctx pg_hmac_ctx; extern pg_hmac_ctx *pg_hmac_create(pg_cryptohash_type type); extern int pg_hmac_init(pg_hmac_ctx *ctx, const uint8 *key, size_t len); extern int pg_hmac_update(pg_hmac_ctx *ctx, const uint8 *data, size_t len); extern int pg_hmac_final(pg_hmac_ctx *ctx, uint8 *dest, size_t len); extern void pg_hmac_free(pg_hmac_ctx *ctx); extern const char *pg_hmac_error(pg_hmac_ctx *ctx); #endif /* PG_HMAC_H */ odyssey-1.6.0-rc1/sources/include/common/md5.h000066400000000000000000000022451526064202200211360ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * md5.h * Constants and common utilities related to MD5. * * These definitions are needed by both frontend and backend code to work * with MD5-encrypted passwords. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/common/md5.h * *------------------------------------------------------------------------- */ #ifndef PG_MD5_H #define PG_MD5_H /* Size of result generated by MD5 computation */ #define MD5_DIGEST_LENGTH 16 /* Block size for MD5 */ #define MD5_BLOCK_SIZE 64 /* password-related data */ #define MD5_PASSWD_CHARSET "0123456789abcdef" #define MD5_PASSWD_LEN 35 /* Utilities common to all the MD5 implementations, as of md5_common.c */ extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum, const char **errstr); extern bool pg_md5_binary(const void *buff, size_t len, uint8 *outbuf, const char **errstr); extern bool pg_md5_encrypt(const char *passwd, const uint8 *salt, size_t salt_len, char *buf, const char **errstr); #endif /* PG_MD5_H */ odyssey-1.6.0-rc1/sources/include/common/md5_int.h000066400000000000000000000055171526064202200220150ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * md5_int.h * Internal headers for fallback implementation of MD5 * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/common/md5_int.h * *------------------------------------------------------------------------- */ /* $KAME: md5.h,v 1.3 2000/02/22 14:01:18 itojun Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef PG_MD5_INT_H #define PG_MD5_INT_H #include "common/md5.h" #define MD5_BUFLEN 64 /* Context data for MD5 */ typedef struct { union { uint32 md5_state32[4]; uint8 md5_state8[16]; } md5_st; #define md5_sta md5_st.md5_state32[0] #define md5_stb md5_st.md5_state32[1] #define md5_stc md5_st.md5_state32[2] #define md5_std md5_st.md5_state32[3] #define md5_st8 md5_st.md5_state8 union { uint64 md5_count64; uint8 md5_count8[8]; } md5_count; #define md5_n md5_count.md5_count64 #define md5_n8 md5_count.md5_count8 unsigned int md5_i; uint8 md5_buf[MD5_BUFLEN]; } pg_md5_ctx; /* Interface routines for MD5 */ extern void pg_md5_init(pg_md5_ctx *ctx); extern void pg_md5_update(pg_md5_ctx *ctx, const uint8 *data, size_t len); extern void pg_md5_final(pg_md5_ctx *ctx, uint8 *dest); #endif /* PG_MD5_INT_H */ odyssey-1.6.0-rc1/sources/include/common/saslprep.h000066400000000000000000000015201526064202200222750ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * saslprep.h * SASLprep normalization, for SCRAM authentication * * These definitions are used by both frontend and backend code. * * Copyright (c) 2017-2025, PostgreSQL Global Development Group * * src/include/common/saslprep.h * *------------------------------------------------------------------------- */ #ifndef SASLPREP_H #define SASLPREP_H /* * Return codes for pg_saslprep() function. */ typedef enum { SASLPREP_SUCCESS = 0, SASLPREP_OOM = -1, /* out of memory (only in frontend) */ SASLPREP_INVALID_UTF8 = -2, /* input is not a valid UTF-8 string */ SASLPREP_PROHIBITED = -3, /* output would contain prohibited characters */ } pg_saslprep_rc; extern pg_saslprep_rc pg_saslprep(const char *input, char **output); #endif /* SASLPREP_H */ odyssey-1.6.0-rc1/sources/include/common/scram-common.h000066400000000000000000000046111526064202200230430ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * scram-common.h * Declarations for helper functions used for SCRAM authentication * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/common/scram-common.h * *------------------------------------------------------------------------- */ #ifndef SCRAM_COMMON_H #define SCRAM_COMMON_H #include "common/cryptohash.h" #include "common/sha2.h" /* Name of SCRAM mechanisms per IANA */ #define SCRAM_SHA_256_NAME "SCRAM-SHA-256" #define SCRAM_SHA_256_PLUS_NAME "SCRAM-SHA-256-PLUS" /* with channel binding */ /* Length of SCRAM keys (client and server) */ #define SCRAM_SHA_256_KEY_LEN PG_SHA256_DIGEST_LENGTH /* * Size of buffers used internally by SCRAM routines, that should be the * maximum of SCRAM_SHA_*_KEY_LEN among the hash methods supported. */ #define SCRAM_MAX_KEY_LEN SCRAM_SHA_256_KEY_LEN /* * Size of random nonce generated in the authentication exchange. This * is in "raw" number of bytes, the actual nonces sent over the wire are * encoded using only ASCII-printable characters. */ #define SCRAM_RAW_NONCE_LEN 18 /* * Length of salt when generating new secrets, in bytes. (It will be stored * and sent over the wire encoded in Base64.) 16 bytes is what the example in * RFC 7677 uses. */ #define SCRAM_DEFAULT_SALT_LEN 16 /* * Default number of iterations when generating secret. Should be at least * 4096 per RFC 7677. */ #define SCRAM_SHA_256_DEFAULT_ITERATIONS 4096 extern int scram_SaltedPassword(const char *password, pg_cryptohash_type hash_type, int key_length, const uint8 *salt, int saltlen, int iterations, uint8 *result, const char **errstr); extern int scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length, uint8 *result, const char **errstr); extern int scram_ClientKey(const uint8 *salted_password, pg_cryptohash_type hash_type, int key_length, uint8 *result, const char **errstr); extern int scram_ServerKey(const uint8 *salted_password, pg_cryptohash_type hash_type, int key_length, uint8 *result, const char **errstr); extern char *scram_build_secret(pg_cryptohash_type hash_type, int key_length, const uint8 *salt, int saltlen, int iterations, const char *password, const char **errstr); #endif /* SCRAM_COMMON_H */ odyssey-1.6.0-rc1/sources/include/common/sha1.h000066400000000000000000000011031526064202200212750ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * sha1.h * Constants related to SHA1. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/common/sha1.h * *------------------------------------------------------------------------- */ #ifndef PG_SHA1_H #define PG_SHA1_H /* Size of result generated by SHA1 computation */ #define SHA1_DIGEST_LENGTH 20 /* Block size for SHA1 */ #define SHA1_BLOCK_SIZE 64 #endif /* PG_SHA1_H */ odyssey-1.6.0-rc1/sources/include/common/sha2.h000066400000000000000000000021631526064202200213050ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * sha2.h * Constants related to SHA224, 256, 384 AND 512. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/include/common/sha2.h * *------------------------------------------------------------------------- */ #ifndef _PG_SHA2_H_ #define _PG_SHA2_H_ /*** SHA224/256/384/512 Various Length Definitions ***********************/ #define PG_SHA224_BLOCK_LENGTH 64 #define PG_SHA224_DIGEST_LENGTH 28 #define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1) #define PG_SHA256_BLOCK_LENGTH 64 #define PG_SHA256_DIGEST_LENGTH 32 #define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1) #define PG_SHA384_BLOCK_LENGTH 128 #define PG_SHA384_DIGEST_LENGTH 48 #define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1) #define PG_SHA512_BLOCK_LENGTH 128 #define PG_SHA512_DIGEST_LENGTH 64 #define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1) #endif /* _PG_SHA2_H_ */ odyssey-1.6.0-rc1/sources/include/common/string.h000066400000000000000000000030511526064202200217530ustar00rootroot00000000000000/* * string.h * string handling helpers * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/common/string.h */ #ifndef COMMON_STRING_H #define COMMON_STRING_H #include typedef struct StringInfoData *StringInfo; /* avoid including stringinfo.h * here */ typedef struct PromptInterruptContext { /* To avoid including here, jmpbuf is declared "void *" */ void *jmpbuf; /* existing longjmp buffer */ volatile sig_atomic_t *enabled; /* flag that enables longjmp-on-interrupt */ bool canceled; /* indicates whether cancellation occurred */ } PromptInterruptContext; /* functions in src/common/string.c */ extern bool pg_str_endswith(const char *str, const char *end); extern int strtoint(const char *restrict str, char **restrict endptr, int base); extern char *pg_clean_ascii(const char *str, int alloc_flags); extern int pg_strip_crlf(char *str); extern bool pg_is_ascii(const char *str); /* functions in src/common/pg_get_line.c */ extern char *pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx); extern bool pg_get_line_buf(FILE *stream, StringInfo buf); extern bool pg_get_line_append(FILE *stream, StringInfo buf, PromptInterruptContext *prompt_ctx); /* functions in src/common/sprompt.c */ extern char *simple_prompt(const char *prompt, bool echo); extern char *simple_prompt_extended(const char *prompt, bool echo, PromptInterruptContext *prompt_ctx); #endif /* COMMON_STRING_H */ odyssey-1.6.0-rc1/sources/include/common/unicode_east_asian_fw_table.h000066400000000000000000000054211526064202200261300ustar00rootroot00000000000000/* generated by src/common/unicode/generate-unicode_east_asian_fw_table.pl, do not edit */ static const struct mbinterval east_asian_fw[] = { { 0x1100, 0x115F }, { 0x231A, 0x231B }, { 0x2329, 0x232A }, { 0x23E9, 0x23EC }, { 0x23F0, 0x23F0 }, { 0x23F3, 0x23F3 }, { 0x25FD, 0x25FE }, { 0x2614, 0x2615 }, { 0x2630, 0x2637 }, { 0x2648, 0x2653 }, { 0x267F, 0x267F }, { 0x268A, 0x268F }, { 0x2693, 0x2693 }, { 0x26A1, 0x26A1 }, { 0x26AA, 0x26AB }, { 0x26BD, 0x26BE }, { 0x26C4, 0x26C5 }, { 0x26CE, 0x26CE }, { 0x26D4, 0x26D4 }, { 0x26EA, 0x26EA }, { 0x26F2, 0x26F3 }, { 0x26F5, 0x26F5 }, { 0x26FA, 0x26FA }, { 0x26FD, 0x26FD }, { 0x2705, 0x2705 }, { 0x270A, 0x270B }, { 0x2728, 0x2728 }, { 0x274C, 0x274C }, { 0x274E, 0x274E }, { 0x2753, 0x2755 }, { 0x2757, 0x2757 }, { 0x2795, 0x2797 }, { 0x27B0, 0x27B0 }, { 0x27BF, 0x27BF }, { 0x2B1B, 0x2B1C }, { 0x2B50, 0x2B50 }, { 0x2B55, 0x2B55 }, { 0x2E80, 0x2E99 }, { 0x2E9B, 0x2EF3 }, { 0x2F00, 0x2FD5 }, { 0x2FF0, 0x303E }, { 0x3041, 0x3096 }, { 0x3099, 0x30FF }, { 0x3105, 0x312F }, { 0x3131, 0x318E }, { 0x3190, 0x31E5 }, { 0x31EF, 0x321E }, { 0x3220, 0x3247 }, { 0x3250, 0xA48C }, { 0xA490, 0xA4C6 }, { 0xA960, 0xA97C }, { 0xAC00, 0xD7A3 }, { 0xF900, 0xFAFF }, { 0xFE10, 0xFE19 }, { 0xFE30, 0xFE52 }, { 0xFE54, 0xFE66 }, { 0xFE68, 0xFE6B }, { 0xFF01, 0xFF60 }, { 0xFFE0, 0xFFE6 }, { 0x16FE0, 0x16FE4 }, { 0x16FF0, 0x16FF1 }, { 0x17000, 0x187F7 }, { 0x18800, 0x18CD5 }, { 0x18CFF, 0x18D08 }, { 0x1AFF0, 0x1AFF3 }, { 0x1AFF5, 0x1AFFB }, { 0x1AFFD, 0x1AFFE }, { 0x1B000, 0x1B122 }, { 0x1B132, 0x1B132 }, { 0x1B150, 0x1B152 }, { 0x1B155, 0x1B155 }, { 0x1B164, 0x1B167 }, { 0x1B170, 0x1B2FB }, { 0x1D300, 0x1D356 }, { 0x1D360, 0x1D376 }, { 0x1F004, 0x1F004 }, { 0x1F0CF, 0x1F0CF }, { 0x1F18E, 0x1F18E }, { 0x1F191, 0x1F19A }, { 0x1F200, 0x1F202 }, { 0x1F210, 0x1F23B }, { 0x1F240, 0x1F248 }, { 0x1F250, 0x1F251 }, { 0x1F260, 0x1F265 }, { 0x1F300, 0x1F320 }, { 0x1F32D, 0x1F335 }, { 0x1F337, 0x1F37C }, { 0x1F37E, 0x1F393 }, { 0x1F3A0, 0x1F3CA }, { 0x1F3CF, 0x1F3D3 }, { 0x1F3E0, 0x1F3F0 }, { 0x1F3F4, 0x1F3F4 }, { 0x1F3F8, 0x1F43E }, { 0x1F440, 0x1F440 }, { 0x1F442, 0x1F4FC }, { 0x1F4FF, 0x1F53D }, { 0x1F54B, 0x1F54E }, { 0x1F550, 0x1F567 }, { 0x1F57A, 0x1F57A }, { 0x1F595, 0x1F596 }, { 0x1F5A4, 0x1F5A4 }, { 0x1F5FB, 0x1F64F }, { 0x1F680, 0x1F6C5 }, { 0x1F6CC, 0x1F6CC }, { 0x1F6D0, 0x1F6D2 }, { 0x1F6D5, 0x1F6D7 }, { 0x1F6DC, 0x1F6DF }, { 0x1F6EB, 0x1F6EC }, { 0x1F6F4, 0x1F6FC }, { 0x1F7E0, 0x1F7EB }, { 0x1F7F0, 0x1F7F0 }, { 0x1F90C, 0x1F93A }, { 0x1F93C, 0x1F945 }, { 0x1F947, 0x1F9FF }, { 0x1FA70, 0x1FA7C }, { 0x1FA80, 0x1FA89 }, { 0x1FA8F, 0x1FAC6 }, { 0x1FACE, 0x1FADC }, { 0x1FADF, 0x1FAE9 }, { 0x1FAF0, 0x1FAF8 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD }, }; odyssey-1.6.0-rc1/sources/include/common/unicode_nonspacing_table.h000066400000000000000000000164521526064202200254720ustar00rootroot00000000000000/* generated by src/common/unicode/generate-unicode_nonspacing_table.pl, do not edit */ static const struct mbinterval nonspacing[] = { { 0x00AD, 0x00AD }, { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0605 }, { 0x0610, 0x061A }, { 0x061C, 0x061C }, { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06DD }, { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x07FD, 0x07FD }, { 0x0816, 0x0819 }, { 0x081B, 0x0823 }, { 0x0825, 0x0827 }, { 0x0829, 0x082D }, { 0x0859, 0x085B }, { 0x0890, 0x089F }, { 0x08CA, 0x0902 }, { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x09FE, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A51 }, { 0x0A70, 0x0A71 }, { 0x0A75, 0x0A75 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0AFA, 0x0B01 }, { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B44 }, { 0x0B4D, 0x0B56 }, { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C00, 0x0C00 }, { 0x0C04, 0x0C04 }, { 0x0C3C, 0x0C3C }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C56 }, { 0x0C62, 0x0C63 }, { 0x0C81, 0x0C81 }, { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 }, { 0x0D00, 0x0D01 }, { 0x0D3B, 0x0D3C }, { 0x0D41, 0x0D44 }, { 0x0D4D, 0x0D4D }, { 0x0D62, 0x0D63 }, { 0x0D81, 0x0D81 }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EBC }, { 0x0EC8, 0x0ECE }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F8D, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1037 }, { 0x1039, 0x103A }, { 0x103D, 0x103E }, { 0x1058, 0x1059 }, { 0x105E, 0x1060 }, { 0x1071, 0x1074 }, { 0x1082, 0x1082 }, { 0x1085, 0x1086 }, { 0x108D, 0x108D }, { 0x109D, 0x109D }, { 0x135D, 0x135F }, { 0x1712, 0x1714 }, { 0x1732, 0x1733 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180F }, { 0x1885, 0x1886 }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x1A17, 0x1A18 }, { 0x1A1B, 0x1A1B }, { 0x1A56, 0x1A56 }, { 0x1A58, 0x1A60 }, { 0x1A62, 0x1A62 }, { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7F }, { 0x1AB0, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, { 0x1B6B, 0x1B73 }, { 0x1B80, 0x1B81 }, { 0x1BA2, 0x1BA5 }, { 0x1BA8, 0x1BA9 }, { 0x1BAB, 0x1BAD }, { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 }, { 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 }, { 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE0 }, { 0x1CE2, 0x1CE8 }, { 0x1CED, 0x1CED }, { 0x1CF4, 0x1CF4 }, { 0x1CF8, 0x1CF9 }, { 0x1DC0, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x206F }, { 0x20D0, 0x20F0 }, { 0x2CEF, 0x2CF1 }, { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF }, { 0x302A, 0x302D }, { 0x3099, 0x309A }, { 0xA66F, 0xA672 }, { 0xA674, 0xA67D }, { 0xA69E, 0xA69F }, { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, { 0xA82C, 0xA82C }, { 0xA8C4, 0xA8C5 }, { 0xA8E0, 0xA8F1 }, { 0xA8FF, 0xA8FF }, { 0xA926, 0xA92D }, { 0xA947, 0xA951 }, { 0xA980, 0xA982 }, { 0xA9B3, 0xA9B3 }, { 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BD }, { 0xA9E5, 0xA9E5 }, { 0xAA29, 0xAA2E }, { 0xAA31, 0xAA32 }, { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 }, { 0xAA4C, 0xAA4C }, { 0xAA7C, 0xAA7C }, { 0xAAB0, 0xAAB0 }, { 0xAAB2, 0xAAB4 }, { 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF }, { 0xAAC1, 0xAAC1 }, { 0xAAEC, 0xAAED }, { 0xAAF6, 0xAAF6 }, { 0xABE5, 0xABE5 }, { 0xABE8, 0xABE8 }, { 0xABED, 0xABED }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE2F }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x101FD, 0x101FD }, { 0x102E0, 0x102E0 }, { 0x10376, 0x1037A }, { 0x10A01, 0x10A0F }, { 0x10A38, 0x10A3F }, { 0x10AE5, 0x10AE6 }, { 0x10D24, 0x10D27 }, { 0x10D69, 0x10D6D }, { 0x10EAB, 0x10EAC }, { 0x10EFC, 0x10EFF }, { 0x10F46, 0x10F50 }, { 0x10F82, 0x10F85 }, { 0x11001, 0x11001 }, { 0x11038, 0x11046 }, { 0x11070, 0x11070 }, { 0x11073, 0x11074 }, { 0x1107F, 0x11081 }, { 0x110B3, 0x110B6 }, { 0x110B9, 0x110BA }, { 0x110BD, 0x110BD }, { 0x110C2, 0x110CD }, { 0x11100, 0x11102 }, { 0x11127, 0x1112B }, { 0x1112D, 0x11134 }, { 0x11173, 0x11173 }, { 0x11180, 0x11181 }, { 0x111B6, 0x111BE }, { 0x111C9, 0x111CC }, { 0x111CF, 0x111CF }, { 0x1122F, 0x11231 }, { 0x11234, 0x11234 }, { 0x11236, 0x11237 }, { 0x1123E, 0x1123E }, { 0x11241, 0x11241 }, { 0x112DF, 0x112DF }, { 0x112E3, 0x112EA }, { 0x11300, 0x11301 }, { 0x1133B, 0x1133C }, { 0x11340, 0x11340 }, { 0x11366, 0x11374 }, { 0x113BB, 0x113C0 }, { 0x113CE, 0x113CE }, { 0x113D0, 0x113D0 }, { 0x113D2, 0x113D2 }, { 0x113E1, 0x113E2 }, { 0x11438, 0x1143F }, { 0x11442, 0x11444 }, { 0x11446, 0x11446 }, { 0x1145E, 0x1145E }, { 0x114B3, 0x114B8 }, { 0x114BA, 0x114BA }, { 0x114BF, 0x114C0 }, { 0x114C2, 0x114C3 }, { 0x115B2, 0x115B5 }, { 0x115BC, 0x115BD }, { 0x115BF, 0x115C0 }, { 0x115DC, 0x115DD }, { 0x11633, 0x1163A }, { 0x1163D, 0x1163D }, { 0x1163F, 0x11640 }, { 0x116AB, 0x116AB }, { 0x116AD, 0x116AD }, { 0x116B0, 0x116B5 }, { 0x116B7, 0x116B7 }, { 0x1171D, 0x1171D }, { 0x1171F, 0x1171F }, { 0x11722, 0x11725 }, { 0x11727, 0x1172B }, { 0x1182F, 0x11837 }, { 0x11839, 0x1183A }, { 0x1193B, 0x1193C }, { 0x1193E, 0x1193E }, { 0x11943, 0x11943 }, { 0x119D4, 0x119DB }, { 0x119E0, 0x119E0 }, { 0x11A01, 0x11A0A }, { 0x11A33, 0x11A38 }, { 0x11A3B, 0x11A3E }, { 0x11A47, 0x11A47 }, { 0x11A51, 0x11A56 }, { 0x11A59, 0x11A5B }, { 0x11A8A, 0x11A96 }, { 0x11A98, 0x11A99 }, { 0x11C30, 0x11C3D }, { 0x11C3F, 0x11C3F }, { 0x11C92, 0x11CA7 }, { 0x11CAA, 0x11CB0 }, { 0x11CB2, 0x11CB3 }, { 0x11CB5, 0x11CB6 }, { 0x11D31, 0x11D45 }, { 0x11D47, 0x11D47 }, { 0x11D90, 0x11D91 }, { 0x11D95, 0x11D95 }, { 0x11D97, 0x11D97 }, { 0x11EF3, 0x11EF4 }, { 0x11F00, 0x11F01 }, { 0x11F36, 0x11F3A }, { 0x11F40, 0x11F40 }, { 0x11F42, 0x11F42 }, { 0x11F5A, 0x11F5A }, { 0x13430, 0x13440 }, { 0x13447, 0x13455 }, { 0x1611E, 0x16129 }, { 0x1612D, 0x1612F }, { 0x16AF0, 0x16AF4 }, { 0x16B30, 0x16B36 }, { 0x16F4F, 0x16F4F }, { 0x16F8F, 0x16F92 }, { 0x16FE4, 0x16FE4 }, { 0x1BC9D, 0x1BC9E }, { 0x1BCA0, 0x1BCA3 }, { 0x1CF00, 0x1CF46 }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, { 0x1D242, 0x1D244 }, { 0x1DA00, 0x1DA36 }, { 0x1DA3B, 0x1DA6C }, { 0x1DA75, 0x1DA75 }, { 0x1DA84, 0x1DA84 }, { 0x1DA9B, 0x1DAAF }, { 0x1E000, 0x1E02A }, { 0x1E08F, 0x1E08F }, { 0x1E130, 0x1E136 }, { 0x1E2AE, 0x1E2AE }, { 0x1E2EC, 0x1E2EF }, { 0x1E4EC, 0x1E4EF }, { 0x1E5EE, 0x1E5EF }, { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A }, { 0xE0001, 0xE01EF }, }; odyssey-1.6.0-rc1/sources/include/common/unicode_norm.h000066400000000000000000000016741526064202200231370ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * unicode_norm.h * Routines for normalizing Unicode strings * * These definitions are used by both frontend and backend code. * * Copyright (c) 2017-2025, PostgreSQL Global Development Group * * src/include/common/unicode_norm.h * *------------------------------------------------------------------------- */ #ifndef UNICODE_NORM_H #define UNICODE_NORM_H typedef enum { UNICODE_NFC = 0, UNICODE_NFD = 1, UNICODE_NFKC = 2, UNICODE_NFKD = 3, } UnicodeNormalizationForm; /* see UAX #15 */ typedef enum { UNICODE_NORM_QC_NO = 0, UNICODE_NORM_QC_YES = 1, UNICODE_NORM_QC_MAYBE = -1, } UnicodeNormalizationQC; extern char32_t *unicode_normalize(UnicodeNormalizationForm form, const char32_t *input); extern UnicodeNormalizationQC unicode_is_normalized_quickcheck(UnicodeNormalizationForm form, const char32_t *input); #endif /* UNICODE_NORM_H */ odyssey-1.6.0-rc1/sources/include/common/unicode_norm_table.h000066400000000000000000013333211526064202200243040ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * unicode_norm_table.h * Composition table used for Unicode normalization * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/common/unicode_norm_table.h * *------------------------------------------------------------------------- */ /* * File auto-generated by src/common/unicode/generate-unicode_norm_table.pl, * do not edit. There is deliberately not an #ifndef PG_UNICODE_NORM_TABLE_H * here. */ typedef struct { uint32 codepoint; /* Unicode codepoint */ uint8 comb_class; /* combining class of character */ uint8 dec_size_flags; /* size and flags of decomposition code list */ uint16 dec_index; /* index into UnicodeDecomp_codepoints, or the * decomposition itself if DECOMP_INLINE */ } pg_unicode_decomposition; #define DECOMP_NO_COMPOSE 0x80 /* don't use for re-composition */ #define DECOMP_INLINE \ 0x40 /* decomposition is stored inline in * dec_index */ #define DECOMP_COMPAT 0x20 /* compatibility mapping */ #define DECOMPOSITION_SIZE(x) ((x)->dec_size_flags & 0x1F) #define DECOMPOSITION_NO_COMPOSE(x) \ (((x)->dec_size_flags & (DECOMP_NO_COMPOSE | DECOMP_COMPAT)) != 0) #define DECOMPOSITION_IS_INLINE(x) (((x)->dec_size_flags & DECOMP_INLINE) != 0) #define DECOMPOSITION_IS_COMPAT(x) (((x)->dec_size_flags & DECOMP_COMPAT) != 0) /* Table of Unicode codepoints and their decompositions */ static const pg_unicode_decomposition UnicodeDecompMain[6843] = { { 0x00A0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x00A8, 0, 2 | DECOMP_COMPAT, 0 }, { 0x00AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x00AF, 0, 2 | DECOMP_COMPAT, 2 }, { 0x00B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x00B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x00B4, 0, 2 | DECOMP_COMPAT, 4 }, { 0x00B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BC }, { 0x00B8, 0, 2 | DECOMP_COMPAT, 6 }, { 0x00B9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x00BA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x00BC, 0, 3 | DECOMP_COMPAT, 8 }, { 0x00BD, 0, 3 | DECOMP_COMPAT, 11 }, { 0x00BE, 0, 3 | DECOMP_COMPAT, 14 }, { 0x00C0, 0, 2, 17 }, { 0x00C1, 0, 2, 19 }, { 0x00C2, 0, 2, 21 }, { 0x00C3, 0, 2, 23 }, { 0x00C4, 0, 2, 25 }, { 0x00C5, 0, 2, 27 }, { 0x00C7, 0, 2, 29 }, { 0x00C8, 0, 2, 31 }, { 0x00C9, 0, 2, 33 }, { 0x00CA, 0, 2, 35 }, { 0x00CB, 0, 2, 37 }, { 0x00CC, 0, 2, 39 }, { 0x00CD, 0, 2, 41 }, { 0x00CE, 0, 2, 43 }, { 0x00CF, 0, 2, 45 }, { 0x00D1, 0, 2, 47 }, { 0x00D2, 0, 2, 49 }, { 0x00D3, 0, 2, 51 }, { 0x00D4, 0, 2, 53 }, { 0x00D5, 0, 2, 55 }, { 0x00D6, 0, 2, 57 }, { 0x00D9, 0, 2, 59 }, { 0x00DA, 0, 2, 61 }, { 0x00DB, 0, 2, 63 }, { 0x00DC, 0, 2, 65 }, { 0x00DD, 0, 2, 67 }, { 0x00E0, 0, 2, 69 }, { 0x00E1, 0, 2, 71 }, { 0x00E2, 0, 2, 73 }, { 0x00E3, 0, 2, 75 }, { 0x00E4, 0, 2, 77 }, { 0x00E5, 0, 2, 79 }, { 0x00E7, 0, 2, 81 }, { 0x00E8, 0, 2, 83 }, { 0x00E9, 0, 2, 85 }, { 0x00EA, 0, 2, 87 }, { 0x00EB, 0, 2, 89 }, { 0x00EC, 0, 2, 91 }, { 0x00ED, 0, 2, 93 }, { 0x00EE, 0, 2, 95 }, { 0x00EF, 0, 2, 97 }, { 0x00F1, 0, 2, 99 }, { 0x00F2, 0, 2, 101 }, { 0x00F3, 0, 2, 103 }, { 0x00F4, 0, 2, 105 }, { 0x00F5, 0, 2, 107 }, { 0x00F6, 0, 2, 109 }, { 0x00F9, 0, 2, 111 }, { 0x00FA, 0, 2, 113 }, { 0x00FB, 0, 2, 115 }, { 0x00FC, 0, 2, 117 }, { 0x00FD, 0, 2, 119 }, { 0x00FF, 0, 2, 121 }, { 0x0100, 0, 2, 123 }, { 0x0101, 0, 2, 125 }, { 0x0102, 0, 2, 127 }, { 0x0103, 0, 2, 129 }, { 0x0104, 0, 2, 131 }, { 0x0105, 0, 2, 133 }, { 0x0106, 0, 2, 135 }, { 0x0107, 0, 2, 137 }, { 0x0108, 0, 2, 139 }, { 0x0109, 0, 2, 141 }, { 0x010A, 0, 2, 143 }, { 0x010B, 0, 2, 145 }, { 0x010C, 0, 2, 147 }, { 0x010D, 0, 2, 149 }, { 0x010E, 0, 2, 151 }, { 0x010F, 0, 2, 153 }, { 0x0112, 0, 2, 155 }, { 0x0113, 0, 2, 157 }, { 0x0114, 0, 2, 159 }, { 0x0115, 0, 2, 161 }, { 0x0116, 0, 2, 163 }, { 0x0117, 0, 2, 165 }, { 0x0118, 0, 2, 167 }, { 0x0119, 0, 2, 169 }, { 0x011A, 0, 2, 171 }, { 0x011B, 0, 2, 173 }, { 0x011C, 0, 2, 175 }, { 0x011D, 0, 2, 177 }, { 0x011E, 0, 2, 179 }, { 0x011F, 0, 2, 181 }, { 0x0120, 0, 2, 183 }, { 0x0121, 0, 2, 185 }, { 0x0122, 0, 2, 187 }, { 0x0123, 0, 2, 189 }, { 0x0124, 0, 2, 191 }, { 0x0125, 0, 2, 193 }, { 0x0128, 0, 2, 195 }, { 0x0129, 0, 2, 197 }, { 0x012A, 0, 2, 199 }, { 0x012B, 0, 2, 201 }, { 0x012C, 0, 2, 203 }, { 0x012D, 0, 2, 205 }, { 0x012E, 0, 2, 207 }, { 0x012F, 0, 2, 209 }, { 0x0130, 0, 2, 211 }, { 0x0132, 0, 2 | DECOMP_COMPAT, 213 }, { 0x0133, 0, 2 | DECOMP_COMPAT, 215 }, { 0x0134, 0, 2, 217 }, { 0x0135, 0, 2, 219 }, { 0x0136, 0, 2, 221 }, { 0x0137, 0, 2, 223 }, { 0x0139, 0, 2, 225 }, { 0x013A, 0, 2, 227 }, { 0x013B, 0, 2, 229 }, { 0x013C, 0, 2, 231 }, { 0x013D, 0, 2, 233 }, { 0x013E, 0, 2, 235 }, { 0x013F, 0, 2 | DECOMP_COMPAT, 237 }, { 0x0140, 0, 2 | DECOMP_COMPAT, 239 }, { 0x0143, 0, 2, 241 }, { 0x0144, 0, 2, 243 }, { 0x0145, 0, 2, 245 }, { 0x0146, 0, 2, 247 }, { 0x0147, 0, 2, 249 }, { 0x0148, 0, 2, 251 }, { 0x0149, 0, 2 | DECOMP_COMPAT, 253 }, { 0x014C, 0, 2, 255 }, { 0x014D, 0, 2, 257 }, { 0x014E, 0, 2, 259 }, { 0x014F, 0, 2, 261 }, { 0x0150, 0, 2, 263 }, { 0x0151, 0, 2, 265 }, { 0x0154, 0, 2, 267 }, { 0x0155, 0, 2, 269 }, { 0x0156, 0, 2, 271 }, { 0x0157, 0, 2, 273 }, { 0x0158, 0, 2, 275 }, { 0x0159, 0, 2, 277 }, { 0x015A, 0, 2, 279 }, { 0x015B, 0, 2, 281 }, { 0x015C, 0, 2, 283 }, { 0x015D, 0, 2, 285 }, { 0x015E, 0, 2, 287 }, { 0x015F, 0, 2, 289 }, { 0x0160, 0, 2, 291 }, { 0x0161, 0, 2, 293 }, { 0x0162, 0, 2, 295 }, { 0x0163, 0, 2, 297 }, { 0x0164, 0, 2, 299 }, { 0x0165, 0, 2, 301 }, { 0x0168, 0, 2, 303 }, { 0x0169, 0, 2, 305 }, { 0x016A, 0, 2, 307 }, { 0x016B, 0, 2, 309 }, { 0x016C, 0, 2, 311 }, { 0x016D, 0, 2, 313 }, { 0x016E, 0, 2, 315 }, { 0x016F, 0, 2, 317 }, { 0x0170, 0, 2, 319 }, { 0x0171, 0, 2, 321 }, { 0x0172, 0, 2, 323 }, { 0x0173, 0, 2, 325 }, { 0x0174, 0, 2, 327 }, { 0x0175, 0, 2, 329 }, { 0x0176, 0, 2, 331 }, { 0x0177, 0, 2, 333 }, { 0x0178, 0, 2, 335 }, { 0x0179, 0, 2, 337 }, { 0x017A, 0, 2, 339 }, { 0x017B, 0, 2, 341 }, { 0x017C, 0, 2, 343 }, { 0x017D, 0, 2, 345 }, { 0x017E, 0, 2, 347 }, { 0x017F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x01A0, 0, 2, 349 }, { 0x01A1, 0, 2, 351 }, { 0x01AF, 0, 2, 353 }, { 0x01B0, 0, 2, 355 }, { 0x01C4, 0, 2 | DECOMP_COMPAT, 357 }, { 0x01C5, 0, 2 | DECOMP_COMPAT, 359 }, { 0x01C6, 0, 2 | DECOMP_COMPAT, 361 }, { 0x01C7, 0, 2 | DECOMP_COMPAT, 363 }, { 0x01C8, 0, 2 | DECOMP_COMPAT, 365 }, { 0x01C9, 0, 2 | DECOMP_COMPAT, 367 }, { 0x01CA, 0, 2 | DECOMP_COMPAT, 369 }, { 0x01CB, 0, 2 | DECOMP_COMPAT, 371 }, { 0x01CC, 0, 2 | DECOMP_COMPAT, 373 }, { 0x01CD, 0, 2, 375 }, { 0x01CE, 0, 2, 377 }, { 0x01CF, 0, 2, 379 }, { 0x01D0, 0, 2, 381 }, { 0x01D1, 0, 2, 383 }, { 0x01D2, 0, 2, 385 }, { 0x01D3, 0, 2, 387 }, { 0x01D4, 0, 2, 389 }, { 0x01D5, 0, 2, 391 }, { 0x01D6, 0, 2, 393 }, { 0x01D7, 0, 2, 395 }, { 0x01D8, 0, 2, 397 }, { 0x01D9, 0, 2, 399 }, { 0x01DA, 0, 2, 401 }, { 0x01DB, 0, 2, 403 }, { 0x01DC, 0, 2, 405 }, { 0x01DE, 0, 2, 407 }, { 0x01DF, 0, 2, 409 }, { 0x01E0, 0, 2, 411 }, { 0x01E1, 0, 2, 413 }, { 0x01E2, 0, 2, 415 }, { 0x01E3, 0, 2, 417 }, { 0x01E6, 0, 2, 419 }, { 0x01E7, 0, 2, 421 }, { 0x01E8, 0, 2, 423 }, { 0x01E9, 0, 2, 425 }, { 0x01EA, 0, 2, 427 }, { 0x01EB, 0, 2, 429 }, { 0x01EC, 0, 2, 431 }, { 0x01ED, 0, 2, 433 }, { 0x01EE, 0, 2, 435 }, { 0x01EF, 0, 2, 437 }, { 0x01F0, 0, 2, 439 }, { 0x01F1, 0, 2 | DECOMP_COMPAT, 441 }, { 0x01F2, 0, 2 | DECOMP_COMPAT, 443 }, { 0x01F3, 0, 2 | DECOMP_COMPAT, 445 }, { 0x01F4, 0, 2, 447 }, { 0x01F5, 0, 2, 449 }, { 0x01F8, 0, 2, 451 }, { 0x01F9, 0, 2, 453 }, { 0x01FA, 0, 2, 455 }, { 0x01FB, 0, 2, 457 }, { 0x01FC, 0, 2, 459 }, { 0x01FD, 0, 2, 461 }, { 0x01FE, 0, 2, 463 }, { 0x01FF, 0, 2, 465 }, { 0x0200, 0, 2, 467 }, { 0x0201, 0, 2, 469 }, { 0x0202, 0, 2, 471 }, { 0x0203, 0, 2, 473 }, { 0x0204, 0, 2, 475 }, { 0x0205, 0, 2, 477 }, { 0x0206, 0, 2, 479 }, { 0x0207, 0, 2, 481 }, { 0x0208, 0, 2, 483 }, { 0x0209, 0, 2, 485 }, { 0x020A, 0, 2, 487 }, { 0x020B, 0, 2, 489 }, { 0x020C, 0, 2, 491 }, { 0x020D, 0, 2, 493 }, { 0x020E, 0, 2, 495 }, { 0x020F, 0, 2, 497 }, { 0x0210, 0, 2, 499 }, { 0x0211, 0, 2, 501 }, { 0x0212, 0, 2, 503 }, { 0x0213, 0, 2, 505 }, { 0x0214, 0, 2, 507 }, { 0x0215, 0, 2, 509 }, { 0x0216, 0, 2, 511 }, { 0x0217, 0, 2, 513 }, { 0x0218, 0, 2, 515 }, { 0x0219, 0, 2, 517 }, { 0x021A, 0, 2, 519 }, { 0x021B, 0, 2, 521 }, { 0x021E, 0, 2, 523 }, { 0x021F, 0, 2, 525 }, { 0x0226, 0, 2, 527 }, { 0x0227, 0, 2, 529 }, { 0x0228, 0, 2, 531 }, { 0x0229, 0, 2, 533 }, { 0x022A, 0, 2, 535 }, { 0x022B, 0, 2, 537 }, { 0x022C, 0, 2, 539 }, { 0x022D, 0, 2, 541 }, { 0x022E, 0, 2, 543 }, { 0x022F, 0, 2, 545 }, { 0x0230, 0, 2, 547 }, { 0x0231, 0, 2, 549 }, { 0x0232, 0, 2, 551 }, { 0x0233, 0, 2, 553 }, { 0x02B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x02B1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0266 }, { 0x02B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x02B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x02B4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0279 }, { 0x02B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x027B }, { 0x02B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0281 }, { 0x02B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x02B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x02D8, 0, 2 | DECOMP_COMPAT, 555 }, { 0x02D9, 0, 2 | DECOMP_COMPAT, 557 }, { 0x02DA, 0, 2 | DECOMP_COMPAT, 559 }, { 0x02DB, 0, 2 | DECOMP_COMPAT, 561 }, { 0x02DC, 0, 2 | DECOMP_COMPAT, 563 }, { 0x02DD, 0, 2 | DECOMP_COMPAT, 565 }, { 0x02E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0263 }, { 0x02E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x02E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x02E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x02E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0295 }, { 0x0300, 230, 0, 0 }, { 0x0301, 230, 0, 0 }, { 0x0302, 230, 0, 0 }, { 0x0303, 230, 0, 0 }, { 0x0304, 230, 0, 0 }, { 0x0305, 230, 0, 0 }, { 0x0306, 230, 0, 0 }, { 0x0307, 230, 0, 0 }, { 0x0308, 230, 0, 0 }, { 0x0309, 230, 0, 0 }, { 0x030A, 230, 0, 0 }, { 0x030B, 230, 0, 0 }, { 0x030C, 230, 0, 0 }, { 0x030D, 230, 0, 0 }, { 0x030E, 230, 0, 0 }, { 0x030F, 230, 0, 0 }, { 0x0310, 230, 0, 0 }, { 0x0311, 230, 0, 0 }, { 0x0312, 230, 0, 0 }, { 0x0313, 230, 0, 0 }, { 0x0314, 230, 0, 0 }, { 0x0315, 232, 0, 0 }, { 0x0316, 220, 0, 0 }, { 0x0317, 220, 0, 0 }, { 0x0318, 220, 0, 0 }, { 0x0319, 220, 0, 0 }, { 0x031A, 232, 0, 0 }, { 0x031B, 216, 0, 0 }, { 0x031C, 220, 0, 0 }, { 0x031D, 220, 0, 0 }, { 0x031E, 220, 0, 0 }, { 0x031F, 220, 0, 0 }, { 0x0320, 220, 0, 0 }, { 0x0321, 202, 0, 0 }, { 0x0322, 202, 0, 0 }, { 0x0323, 220, 0, 0 }, { 0x0324, 220, 0, 0 }, { 0x0325, 220, 0, 0 }, { 0x0326, 220, 0, 0 }, { 0x0327, 202, 0, 0 }, { 0x0328, 202, 0, 0 }, { 0x0329, 220, 0, 0 }, { 0x032A, 220, 0, 0 }, { 0x032B, 220, 0, 0 }, { 0x032C, 220, 0, 0 }, { 0x032D, 220, 0, 0 }, { 0x032E, 220, 0, 0 }, { 0x032F, 220, 0, 0 }, { 0x0330, 220, 0, 0 }, { 0x0331, 220, 0, 0 }, { 0x0332, 220, 0, 0 }, { 0x0333, 220, 0, 0 }, { 0x0334, 1, 0, 0 }, { 0x0335, 1, 0, 0 }, { 0x0336, 1, 0, 0 }, { 0x0337, 1, 0, 0 }, { 0x0338, 1, 0, 0 }, { 0x0339, 220, 0, 0 }, { 0x033A, 220, 0, 0 }, { 0x033B, 220, 0, 0 }, { 0x033C, 220, 0, 0 }, { 0x033D, 230, 0, 0 }, { 0x033E, 230, 0, 0 }, { 0x033F, 230, 0, 0 }, { 0x0340, 230, 1 | DECOMP_INLINE, 0x0300 }, { 0x0341, 230, 1 | DECOMP_INLINE, 0x0301 }, { 0x0342, 230, 0, 0 }, { 0x0343, 230, 1 | DECOMP_INLINE, 0x0313 }, { 0x0344, 230, 2 | DECOMP_NO_COMPOSE, 567 }, /* non-starter decomposition */ { 0x0345, 240, 0, 0 }, { 0x0346, 230, 0, 0 }, { 0x0347, 220, 0, 0 }, { 0x0348, 220, 0, 0 }, { 0x0349, 220, 0, 0 }, { 0x034A, 230, 0, 0 }, { 0x034B, 230, 0, 0 }, { 0x034C, 230, 0, 0 }, { 0x034D, 220, 0, 0 }, { 0x034E, 220, 0, 0 }, { 0x0350, 230, 0, 0 }, { 0x0351, 230, 0, 0 }, { 0x0352, 230, 0, 0 }, { 0x0353, 220, 0, 0 }, { 0x0354, 220, 0, 0 }, { 0x0355, 220, 0, 0 }, { 0x0356, 220, 0, 0 }, { 0x0357, 230, 0, 0 }, { 0x0358, 232, 0, 0 }, { 0x0359, 220, 0, 0 }, { 0x035A, 220, 0, 0 }, { 0x035B, 230, 0, 0 }, { 0x035C, 233, 0, 0 }, { 0x035D, 234, 0, 0 }, { 0x035E, 234, 0, 0 }, { 0x035F, 233, 0, 0 }, { 0x0360, 234, 0, 0 }, { 0x0361, 234, 0, 0 }, { 0x0362, 233, 0, 0 }, { 0x0363, 230, 0, 0 }, { 0x0364, 230, 0, 0 }, { 0x0365, 230, 0, 0 }, { 0x0366, 230, 0, 0 }, { 0x0367, 230, 0, 0 }, { 0x0368, 230, 0, 0 }, { 0x0369, 230, 0, 0 }, { 0x036A, 230, 0, 0 }, { 0x036B, 230, 0, 0 }, { 0x036C, 230, 0, 0 }, { 0x036D, 230, 0, 0 }, { 0x036E, 230, 0, 0 }, { 0x036F, 230, 0, 0 }, { 0x0374, 0, 1 | DECOMP_INLINE, 0x02B9 }, { 0x037A, 0, 2 | DECOMP_COMPAT, 569 }, { 0x037E, 0, 1 | DECOMP_INLINE, 0x003B }, { 0x0384, 0, 2 | DECOMP_COMPAT, 571 }, { 0x0385, 0, 2, 573 }, { 0x0386, 0, 2, 575 }, { 0x0387, 0, 1 | DECOMP_INLINE, 0x00B7 }, { 0x0388, 0, 2, 577 }, { 0x0389, 0, 2, 579 }, { 0x038A, 0, 2, 581 }, { 0x038C, 0, 2, 583 }, { 0x038E, 0, 2, 585 }, { 0x038F, 0, 2, 587 }, { 0x0390, 0, 2, 589 }, { 0x03AA, 0, 2, 591 }, { 0x03AB, 0, 2, 593 }, { 0x03AC, 0, 2, 595 }, { 0x03AD, 0, 2, 597 }, { 0x03AE, 0, 2, 599 }, { 0x03AF, 0, 2, 601 }, { 0x03B0, 0, 2, 603 }, { 0x03CA, 0, 2, 605 }, { 0x03CB, 0, 2, 607 }, { 0x03CC, 0, 2, 609 }, { 0x03CD, 0, 2, 611 }, { 0x03CE, 0, 2, 613 }, { 0x03D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x03D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x03D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A5 }, { 0x03D3, 0, 2, 615 }, { 0x03D4, 0, 2, 617 }, { 0x03D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x03D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x03F0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BA }, { 0x03F1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x03F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C2 }, { 0x03F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0398 }, { 0x03F5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B5 }, { 0x03F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A3 }, { 0x0400, 0, 2, 619 }, { 0x0401, 0, 2, 621 }, { 0x0403, 0, 2, 623 }, { 0x0407, 0, 2, 625 }, { 0x040C, 0, 2, 627 }, { 0x040D, 0, 2, 629 }, { 0x040E, 0, 2, 631 }, { 0x0419, 0, 2, 633 }, { 0x0439, 0, 2, 635 }, { 0x0450, 0, 2, 637 }, { 0x0451, 0, 2, 639 }, { 0x0453, 0, 2, 641 }, { 0x0457, 0, 2, 643 }, { 0x045C, 0, 2, 645 }, { 0x045D, 0, 2, 647 }, { 0x045E, 0, 2, 649 }, { 0x0476, 0, 2, 651 }, { 0x0477, 0, 2, 653 }, { 0x0483, 230, 0, 0 }, { 0x0484, 230, 0, 0 }, { 0x0485, 230, 0, 0 }, { 0x0486, 230, 0, 0 }, { 0x0487, 230, 0, 0 }, { 0x04C1, 0, 2, 655 }, { 0x04C2, 0, 2, 657 }, { 0x04D0, 0, 2, 659 }, { 0x04D1, 0, 2, 661 }, { 0x04D2, 0, 2, 663 }, { 0x04D3, 0, 2, 665 }, { 0x04D6, 0, 2, 667 }, { 0x04D7, 0, 2, 669 }, { 0x04DA, 0, 2, 671 }, { 0x04DB, 0, 2, 673 }, { 0x04DC, 0, 2, 675 }, { 0x04DD, 0, 2, 677 }, { 0x04DE, 0, 2, 679 }, { 0x04DF, 0, 2, 681 }, { 0x04E2, 0, 2, 683 }, { 0x04E3, 0, 2, 685 }, { 0x04E4, 0, 2, 687 }, { 0x04E5, 0, 2, 689 }, { 0x04E6, 0, 2, 691 }, { 0x04E7, 0, 2, 693 }, { 0x04EA, 0, 2, 695 }, { 0x04EB, 0, 2, 697 }, { 0x04EC, 0, 2, 699 }, { 0x04ED, 0, 2, 701 }, { 0x04EE, 0, 2, 703 }, { 0x04EF, 0, 2, 705 }, { 0x04F0, 0, 2, 707 }, { 0x04F1, 0, 2, 709 }, { 0x04F2, 0, 2, 711 }, { 0x04F3, 0, 2, 713 }, { 0x04F4, 0, 2, 715 }, { 0x04F5, 0, 2, 717 }, { 0x04F8, 0, 2, 719 }, { 0x04F9, 0, 2, 721 }, { 0x0587, 0, 2 | DECOMP_COMPAT, 723 }, { 0x0591, 220, 0, 0 }, { 0x0592, 230, 0, 0 }, { 0x0593, 230, 0, 0 }, { 0x0594, 230, 0, 0 }, { 0x0595, 230, 0, 0 }, { 0x0596, 220, 0, 0 }, { 0x0597, 230, 0, 0 }, { 0x0598, 230, 0, 0 }, { 0x0599, 230, 0, 0 }, { 0x059A, 222, 0, 0 }, { 0x059B, 220, 0, 0 }, { 0x059C, 230, 0, 0 }, { 0x059D, 230, 0, 0 }, { 0x059E, 230, 0, 0 }, { 0x059F, 230, 0, 0 }, { 0x05A0, 230, 0, 0 }, { 0x05A1, 230, 0, 0 }, { 0x05A2, 220, 0, 0 }, { 0x05A3, 220, 0, 0 }, { 0x05A4, 220, 0, 0 }, { 0x05A5, 220, 0, 0 }, { 0x05A6, 220, 0, 0 }, { 0x05A7, 220, 0, 0 }, { 0x05A8, 230, 0, 0 }, { 0x05A9, 230, 0, 0 }, { 0x05AA, 220, 0, 0 }, { 0x05AB, 230, 0, 0 }, { 0x05AC, 230, 0, 0 }, { 0x05AD, 222, 0, 0 }, { 0x05AE, 228, 0, 0 }, { 0x05AF, 230, 0, 0 }, { 0x05B0, 10, 0, 0 }, { 0x05B1, 11, 0, 0 }, { 0x05B2, 12, 0, 0 }, { 0x05B3, 13, 0, 0 }, { 0x05B4, 14, 0, 0 }, { 0x05B5, 15, 0, 0 }, { 0x05B6, 16, 0, 0 }, { 0x05B7, 17, 0, 0 }, { 0x05B8, 18, 0, 0 }, { 0x05B9, 19, 0, 0 }, { 0x05BA, 19, 0, 0 }, { 0x05BB, 20, 0, 0 }, { 0x05BC, 21, 0, 0 }, { 0x05BD, 22, 0, 0 }, { 0x05BF, 23, 0, 0 }, { 0x05C1, 24, 0, 0 }, { 0x05C2, 25, 0, 0 }, { 0x05C4, 230, 0, 0 }, { 0x05C5, 220, 0, 0 }, { 0x05C7, 18, 0, 0 }, { 0x0610, 230, 0, 0 }, { 0x0611, 230, 0, 0 }, { 0x0612, 230, 0, 0 }, { 0x0613, 230, 0, 0 }, { 0x0614, 230, 0, 0 }, { 0x0615, 230, 0, 0 }, { 0x0616, 230, 0, 0 }, { 0x0617, 230, 0, 0 }, { 0x0618, 30, 0, 0 }, { 0x0619, 31, 0, 0 }, { 0x061A, 32, 0, 0 }, { 0x0622, 0, 2, 725 }, { 0x0623, 0, 2, 727 }, { 0x0624, 0, 2, 729 }, { 0x0625, 0, 2, 731 }, { 0x0626, 0, 2, 733 }, { 0x064B, 27, 0, 0 }, { 0x064C, 28, 0, 0 }, { 0x064D, 29, 0, 0 }, { 0x064E, 30, 0, 0 }, { 0x064F, 31, 0, 0 }, { 0x0650, 32, 0, 0 }, { 0x0651, 33, 0, 0 }, { 0x0652, 34, 0, 0 }, { 0x0653, 230, 0, 0 }, { 0x0654, 230, 0, 0 }, { 0x0655, 220, 0, 0 }, { 0x0656, 220, 0, 0 }, { 0x0657, 230, 0, 0 }, { 0x0658, 230, 0, 0 }, { 0x0659, 230, 0, 0 }, { 0x065A, 230, 0, 0 }, { 0x065B, 230, 0, 0 }, { 0x065C, 220, 0, 0 }, { 0x065D, 230, 0, 0 }, { 0x065E, 230, 0, 0 }, { 0x065F, 220, 0, 0 }, { 0x0670, 35, 0, 0 }, { 0x0675, 0, 2 | DECOMP_COMPAT, 735 }, { 0x0676, 0, 2 | DECOMP_COMPAT, 737 }, { 0x0677, 0, 2 | DECOMP_COMPAT, 739 }, { 0x0678, 0, 2 | DECOMP_COMPAT, 741 }, { 0x06C0, 0, 2, 743 }, { 0x06C2, 0, 2, 745 }, { 0x06D3, 0, 2, 747 }, { 0x06D6, 230, 0, 0 }, { 0x06D7, 230, 0, 0 }, { 0x06D8, 230, 0, 0 }, { 0x06D9, 230, 0, 0 }, { 0x06DA, 230, 0, 0 }, { 0x06DB, 230, 0, 0 }, { 0x06DC, 230, 0, 0 }, { 0x06DF, 230, 0, 0 }, { 0x06E0, 230, 0, 0 }, { 0x06E1, 230, 0, 0 }, { 0x06E2, 230, 0, 0 }, { 0x06E3, 220, 0, 0 }, { 0x06E4, 230, 0, 0 }, { 0x06E7, 230, 0, 0 }, { 0x06E8, 230, 0, 0 }, { 0x06EA, 220, 0, 0 }, { 0x06EB, 230, 0, 0 }, { 0x06EC, 230, 0, 0 }, { 0x06ED, 220, 0, 0 }, { 0x0711, 36, 0, 0 }, { 0x0730, 230, 0, 0 }, { 0x0731, 220, 0, 0 }, { 0x0732, 230, 0, 0 }, { 0x0733, 230, 0, 0 }, { 0x0734, 220, 0, 0 }, { 0x0735, 230, 0, 0 }, { 0x0736, 230, 0, 0 }, { 0x0737, 220, 0, 0 }, { 0x0738, 220, 0, 0 }, { 0x0739, 220, 0, 0 }, { 0x073A, 230, 0, 0 }, { 0x073B, 220, 0, 0 }, { 0x073C, 220, 0, 0 }, { 0x073D, 230, 0, 0 }, { 0x073E, 220, 0, 0 }, { 0x073F, 230, 0, 0 }, { 0x0740, 230, 0, 0 }, { 0x0741, 230, 0, 0 }, { 0x0742, 220, 0, 0 }, { 0x0743, 230, 0, 0 }, { 0x0744, 220, 0, 0 }, { 0x0745, 230, 0, 0 }, { 0x0746, 220, 0, 0 }, { 0x0747, 230, 0, 0 }, { 0x0748, 220, 0, 0 }, { 0x0749, 230, 0, 0 }, { 0x074A, 230, 0, 0 }, { 0x07EB, 230, 0, 0 }, { 0x07EC, 230, 0, 0 }, { 0x07ED, 230, 0, 0 }, { 0x07EE, 230, 0, 0 }, { 0x07EF, 230, 0, 0 }, { 0x07F0, 230, 0, 0 }, { 0x07F1, 230, 0, 0 }, { 0x07F2, 220, 0, 0 }, { 0x07F3, 230, 0, 0 }, { 0x07FD, 220, 0, 0 }, { 0x0816, 230, 0, 0 }, { 0x0817, 230, 0, 0 }, { 0x0818, 230, 0, 0 }, { 0x0819, 230, 0, 0 }, { 0x081B, 230, 0, 0 }, { 0x081C, 230, 0, 0 }, { 0x081D, 230, 0, 0 }, { 0x081E, 230, 0, 0 }, { 0x081F, 230, 0, 0 }, { 0x0820, 230, 0, 0 }, { 0x0821, 230, 0, 0 }, { 0x0822, 230, 0, 0 }, { 0x0823, 230, 0, 0 }, { 0x0825, 230, 0, 0 }, { 0x0826, 230, 0, 0 }, { 0x0827, 230, 0, 0 }, { 0x0829, 230, 0, 0 }, { 0x082A, 230, 0, 0 }, { 0x082B, 230, 0, 0 }, { 0x082C, 230, 0, 0 }, { 0x082D, 230, 0, 0 }, { 0x0859, 220, 0, 0 }, { 0x085A, 220, 0, 0 }, { 0x085B, 220, 0, 0 }, { 0x0897, 230, 0, 0 }, { 0x0898, 230, 0, 0 }, { 0x0899, 220, 0, 0 }, { 0x089A, 220, 0, 0 }, { 0x089B, 220, 0, 0 }, { 0x089C, 230, 0, 0 }, { 0x089D, 230, 0, 0 }, { 0x089E, 230, 0, 0 }, { 0x089F, 230, 0, 0 }, { 0x08CA, 230, 0, 0 }, { 0x08CB, 230, 0, 0 }, { 0x08CC, 230, 0, 0 }, { 0x08CD, 230, 0, 0 }, { 0x08CE, 230, 0, 0 }, { 0x08CF, 220, 0, 0 }, { 0x08D0, 220, 0, 0 }, { 0x08D1, 220, 0, 0 }, { 0x08D2, 220, 0, 0 }, { 0x08D3, 220, 0, 0 }, { 0x08D4, 230, 0, 0 }, { 0x08D5, 230, 0, 0 }, { 0x08D6, 230, 0, 0 }, { 0x08D7, 230, 0, 0 }, { 0x08D8, 230, 0, 0 }, { 0x08D9, 230, 0, 0 }, { 0x08DA, 230, 0, 0 }, { 0x08DB, 230, 0, 0 }, { 0x08DC, 230, 0, 0 }, { 0x08DD, 230, 0, 0 }, { 0x08DE, 230, 0, 0 }, { 0x08DF, 230, 0, 0 }, { 0x08E0, 230, 0, 0 }, { 0x08E1, 230, 0, 0 }, { 0x08E3, 220, 0, 0 }, { 0x08E4, 230, 0, 0 }, { 0x08E5, 230, 0, 0 }, { 0x08E6, 220, 0, 0 }, { 0x08E7, 230, 0, 0 }, { 0x08E8, 230, 0, 0 }, { 0x08E9, 220, 0, 0 }, { 0x08EA, 230, 0, 0 }, { 0x08EB, 230, 0, 0 }, { 0x08EC, 230, 0, 0 }, { 0x08ED, 220, 0, 0 }, { 0x08EE, 220, 0, 0 }, { 0x08EF, 220, 0, 0 }, { 0x08F0, 27, 0, 0 }, { 0x08F1, 28, 0, 0 }, { 0x08F2, 29, 0, 0 }, { 0x08F3, 230, 0, 0 }, { 0x08F4, 230, 0, 0 }, { 0x08F5, 230, 0, 0 }, { 0x08F6, 220, 0, 0 }, { 0x08F7, 230, 0, 0 }, { 0x08F8, 230, 0, 0 }, { 0x08F9, 220, 0, 0 }, { 0x08FA, 220, 0, 0 }, { 0x08FB, 230, 0, 0 }, { 0x08FC, 230, 0, 0 }, { 0x08FD, 230, 0, 0 }, { 0x08FE, 230, 0, 0 }, { 0x08FF, 230, 0, 0 }, { 0x0929, 0, 2, 749 }, { 0x0931, 0, 2, 751 }, { 0x0934, 0, 2, 753 }, { 0x093C, 7, 0, 0 }, { 0x094D, 9, 0, 0 }, { 0x0951, 230, 0, 0 }, { 0x0952, 220, 0, 0 }, { 0x0953, 230, 0, 0 }, { 0x0954, 230, 0, 0 }, { 0x0958, 0, 2 | DECOMP_NO_COMPOSE, 755 }, /* in exclusion list */ { 0x0959, 0, 2 | DECOMP_NO_COMPOSE, 757 }, /* in exclusion list */ { 0x095A, 0, 2 | DECOMP_NO_COMPOSE, 759 }, /* in exclusion list */ { 0x095B, 0, 2 | DECOMP_NO_COMPOSE, 761 }, /* in exclusion list */ { 0x095C, 0, 2 | DECOMP_NO_COMPOSE, 763 }, /* in exclusion list */ { 0x095D, 0, 2 | DECOMP_NO_COMPOSE, 765 }, /* in exclusion list */ { 0x095E, 0, 2 | DECOMP_NO_COMPOSE, 767 }, /* in exclusion list */ { 0x095F, 0, 2 | DECOMP_NO_COMPOSE, 769 }, /* in exclusion list */ { 0x09BC, 7, 0, 0 }, { 0x09CB, 0, 2, 771 }, { 0x09CC, 0, 2, 773 }, { 0x09CD, 9, 0, 0 }, { 0x09DC, 0, 2 | DECOMP_NO_COMPOSE, 775 }, /* in exclusion list */ { 0x09DD, 0, 2 | DECOMP_NO_COMPOSE, 777 }, /* in exclusion list */ { 0x09DF, 0, 2 | DECOMP_NO_COMPOSE, 779 }, /* in exclusion list */ { 0x09FE, 230, 0, 0 }, { 0x0A33, 0, 2 | DECOMP_NO_COMPOSE, 781 }, /* in exclusion list */ { 0x0A36, 0, 2 | DECOMP_NO_COMPOSE, 783 }, /* in exclusion list */ { 0x0A3C, 7, 0, 0 }, { 0x0A4D, 9, 0, 0 }, { 0x0A59, 0, 2 | DECOMP_NO_COMPOSE, 785 }, /* in exclusion list */ { 0x0A5A, 0, 2 | DECOMP_NO_COMPOSE, 787 }, /* in exclusion list */ { 0x0A5B, 0, 2 | DECOMP_NO_COMPOSE, 789 }, /* in exclusion list */ { 0x0A5E, 0, 2 | DECOMP_NO_COMPOSE, 791 }, /* in exclusion list */ { 0x0ABC, 7, 0, 0 }, { 0x0ACD, 9, 0, 0 }, { 0x0B3C, 7, 0, 0 }, { 0x0B48, 0, 2, 793 }, { 0x0B4B, 0, 2, 795 }, { 0x0B4C, 0, 2, 797 }, { 0x0B4D, 9, 0, 0 }, { 0x0B5C, 0, 2 | DECOMP_NO_COMPOSE, 799 }, /* in exclusion list */ { 0x0B5D, 0, 2 | DECOMP_NO_COMPOSE, 801 }, /* in exclusion list */ { 0x0B94, 0, 2, 803 }, { 0x0BCA, 0, 2, 805 }, { 0x0BCB, 0, 2, 807 }, { 0x0BCC, 0, 2, 809 }, { 0x0BCD, 9, 0, 0 }, { 0x0C3C, 7, 0, 0 }, { 0x0C48, 0, 2, 811 }, { 0x0C4D, 9, 0, 0 }, { 0x0C55, 84, 0, 0 }, { 0x0C56, 91, 0, 0 }, { 0x0CBC, 7, 0, 0 }, { 0x0CC0, 0, 2, 813 }, { 0x0CC7, 0, 2, 815 }, { 0x0CC8, 0, 2, 817 }, { 0x0CCA, 0, 2, 819 }, { 0x0CCB, 0, 2, 821 }, { 0x0CCD, 9, 0, 0 }, { 0x0D3B, 9, 0, 0 }, { 0x0D3C, 9, 0, 0 }, { 0x0D4A, 0, 2, 823 }, { 0x0D4B, 0, 2, 825 }, { 0x0D4C, 0, 2, 827 }, { 0x0D4D, 9, 0, 0 }, { 0x0DCA, 9, 0, 0 }, { 0x0DDA, 0, 2, 829 }, { 0x0DDC, 0, 2, 831 }, { 0x0DDD, 0, 2, 833 }, { 0x0DDE, 0, 2, 835 }, { 0x0E33, 0, 2 | DECOMP_COMPAT, 837 }, { 0x0E38, 103, 0, 0 }, { 0x0E39, 103, 0, 0 }, { 0x0E3A, 9, 0, 0 }, { 0x0E48, 107, 0, 0 }, { 0x0E49, 107, 0, 0 }, { 0x0E4A, 107, 0, 0 }, { 0x0E4B, 107, 0, 0 }, { 0x0EB3, 0, 2 | DECOMP_COMPAT, 839 }, { 0x0EB8, 118, 0, 0 }, { 0x0EB9, 118, 0, 0 }, { 0x0EBA, 9, 0, 0 }, { 0x0EC8, 122, 0, 0 }, { 0x0EC9, 122, 0, 0 }, { 0x0ECA, 122, 0, 0 }, { 0x0ECB, 122, 0, 0 }, { 0x0EDC, 0, 2 | DECOMP_COMPAT, 841 }, { 0x0EDD, 0, 2 | DECOMP_COMPAT, 843 }, { 0x0F0C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0F0B }, { 0x0F18, 220, 0, 0 }, { 0x0F19, 220, 0, 0 }, { 0x0F35, 220, 0, 0 }, { 0x0F37, 220, 0, 0 }, { 0x0F39, 216, 0, 0 }, { 0x0F43, 0, 2 | DECOMP_NO_COMPOSE, 845 }, /* in exclusion list */ { 0x0F4D, 0, 2 | DECOMP_NO_COMPOSE, 847 }, /* in exclusion list */ { 0x0F52, 0, 2 | DECOMP_NO_COMPOSE, 849 }, /* in exclusion list */ { 0x0F57, 0, 2 | DECOMP_NO_COMPOSE, 851 }, /* in exclusion list */ { 0x0F5C, 0, 2 | DECOMP_NO_COMPOSE, 853 }, /* in exclusion list */ { 0x0F69, 0, 2 | DECOMP_NO_COMPOSE, 855 }, /* in exclusion list */ { 0x0F71, 129, 0, 0 }, { 0x0F72, 130, 0, 0 }, { 0x0F73, 0, 2 | DECOMP_NO_COMPOSE, 857 }, /* non-starter decomposition */ { 0x0F74, 132, 0, 0 }, { 0x0F75, 0, 2 | DECOMP_NO_COMPOSE, 859 }, /* non-starter decomposition */ { 0x0F76, 0, 2 | DECOMP_NO_COMPOSE, 861 }, /* in exclusion list */ { 0x0F77, 0, 2 | DECOMP_COMPAT, 863 }, { 0x0F78, 0, 2 | DECOMP_NO_COMPOSE, 865 }, /* in exclusion list */ { 0x0F79, 0, 2 | DECOMP_COMPAT, 867 }, { 0x0F7A, 130, 0, 0 }, { 0x0F7B, 130, 0, 0 }, { 0x0F7C, 130, 0, 0 }, { 0x0F7D, 130, 0, 0 }, { 0x0F80, 130, 0, 0 }, { 0x0F81, 0, 2 | DECOMP_NO_COMPOSE, 869 }, /* non-starter decomposition */ { 0x0F82, 230, 0, 0 }, { 0x0F83, 230, 0, 0 }, { 0x0F84, 9, 0, 0 }, { 0x0F86, 230, 0, 0 }, { 0x0F87, 230, 0, 0 }, { 0x0F93, 0, 2 | DECOMP_NO_COMPOSE, 871 }, /* in exclusion list */ { 0x0F9D, 0, 2 | DECOMP_NO_COMPOSE, 873 }, /* in exclusion list */ { 0x0FA2, 0, 2 | DECOMP_NO_COMPOSE, 875 }, /* in exclusion list */ { 0x0FA7, 0, 2 | DECOMP_NO_COMPOSE, 877 }, /* in exclusion list */ { 0x0FAC, 0, 2 | DECOMP_NO_COMPOSE, 879 }, /* in exclusion list */ { 0x0FB9, 0, 2 | DECOMP_NO_COMPOSE, 881 }, /* in exclusion list */ { 0x0FC6, 220, 0, 0 }, { 0x1026, 0, 2, 883 }, { 0x1037, 7, 0, 0 }, { 0x1039, 9, 0, 0 }, { 0x103A, 9, 0, 0 }, { 0x108D, 220, 0, 0 }, { 0x10FC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x10DC }, { 0x135D, 230, 0, 0 }, { 0x135E, 230, 0, 0 }, { 0x135F, 230, 0, 0 }, { 0x1714, 9, 0, 0 }, { 0x1715, 9, 0, 0 }, { 0x1734, 9, 0, 0 }, { 0x17D2, 9, 0, 0 }, { 0x17DD, 230, 0, 0 }, { 0x18A9, 228, 0, 0 }, { 0x1939, 222, 0, 0 }, { 0x193A, 230, 0, 0 }, { 0x193B, 220, 0, 0 }, { 0x1A17, 230, 0, 0 }, { 0x1A18, 220, 0, 0 }, { 0x1A60, 9, 0, 0 }, { 0x1A75, 230, 0, 0 }, { 0x1A76, 230, 0, 0 }, { 0x1A77, 230, 0, 0 }, { 0x1A78, 230, 0, 0 }, { 0x1A79, 230, 0, 0 }, { 0x1A7A, 230, 0, 0 }, { 0x1A7B, 230, 0, 0 }, { 0x1A7C, 230, 0, 0 }, { 0x1A7F, 220, 0, 0 }, { 0x1AB0, 230, 0, 0 }, { 0x1AB1, 230, 0, 0 }, { 0x1AB2, 230, 0, 0 }, { 0x1AB3, 230, 0, 0 }, { 0x1AB4, 230, 0, 0 }, { 0x1AB5, 220, 0, 0 }, { 0x1AB6, 220, 0, 0 }, { 0x1AB7, 220, 0, 0 }, { 0x1AB8, 220, 0, 0 }, { 0x1AB9, 220, 0, 0 }, { 0x1ABA, 220, 0, 0 }, { 0x1ABB, 230, 0, 0 }, { 0x1ABC, 230, 0, 0 }, { 0x1ABD, 220, 0, 0 }, { 0x1ABF, 220, 0, 0 }, { 0x1AC0, 220, 0, 0 }, { 0x1AC1, 230, 0, 0 }, { 0x1AC2, 230, 0, 0 }, { 0x1AC3, 220, 0, 0 }, { 0x1AC4, 220, 0, 0 }, { 0x1AC5, 230, 0, 0 }, { 0x1AC6, 230, 0, 0 }, { 0x1AC7, 230, 0, 0 }, { 0x1AC8, 230, 0, 0 }, { 0x1AC9, 230, 0, 0 }, { 0x1ACA, 220, 0, 0 }, { 0x1ACB, 230, 0, 0 }, { 0x1ACC, 230, 0, 0 }, { 0x1ACD, 230, 0, 0 }, { 0x1ACE, 230, 0, 0 }, { 0x1B06, 0, 2, 885 }, { 0x1B08, 0, 2, 887 }, { 0x1B0A, 0, 2, 889 }, { 0x1B0C, 0, 2, 891 }, { 0x1B0E, 0, 2, 893 }, { 0x1B12, 0, 2, 895 }, { 0x1B34, 7, 0, 0 }, { 0x1B3B, 0, 2, 897 }, { 0x1B3D, 0, 2, 899 }, { 0x1B40, 0, 2, 901 }, { 0x1B41, 0, 2, 903 }, { 0x1B43, 0, 2, 905 }, { 0x1B44, 9, 0, 0 }, { 0x1B6B, 230, 0, 0 }, { 0x1B6C, 220, 0, 0 }, { 0x1B6D, 230, 0, 0 }, { 0x1B6E, 230, 0, 0 }, { 0x1B6F, 230, 0, 0 }, { 0x1B70, 230, 0, 0 }, { 0x1B71, 230, 0, 0 }, { 0x1B72, 230, 0, 0 }, { 0x1B73, 230, 0, 0 }, { 0x1BAA, 9, 0, 0 }, { 0x1BAB, 9, 0, 0 }, { 0x1BE6, 7, 0, 0 }, { 0x1BF2, 9, 0, 0 }, { 0x1BF3, 9, 0, 0 }, { 0x1C37, 7, 0, 0 }, { 0x1CD0, 230, 0, 0 }, { 0x1CD1, 230, 0, 0 }, { 0x1CD2, 230, 0, 0 }, { 0x1CD4, 1, 0, 0 }, { 0x1CD5, 220, 0, 0 }, { 0x1CD6, 220, 0, 0 }, { 0x1CD7, 220, 0, 0 }, { 0x1CD8, 220, 0, 0 }, { 0x1CD9, 220, 0, 0 }, { 0x1CDA, 230, 0, 0 }, { 0x1CDB, 230, 0, 0 }, { 0x1CDC, 220, 0, 0 }, { 0x1CDD, 220, 0, 0 }, { 0x1CDE, 220, 0, 0 }, { 0x1CDF, 220, 0, 0 }, { 0x1CE0, 230, 0, 0 }, { 0x1CE2, 1, 0, 0 }, { 0x1CE3, 1, 0, 0 }, { 0x1CE4, 1, 0, 0 }, { 0x1CE5, 1, 0, 0 }, { 0x1CE6, 1, 0, 0 }, { 0x1CE7, 1, 0, 0 }, { 0x1CE8, 1, 0, 0 }, { 0x1CED, 220, 0, 0 }, { 0x1CF4, 230, 0, 0 }, { 0x1CF8, 230, 0, 0 }, { 0x1CF9, 230, 0, 0 }, { 0x1D2C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D2D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00C6 }, { 0x1D2E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D30, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D31, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D32, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x018E }, { 0x1D33, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D34, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D35, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D36, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D37, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D38, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D39, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D3A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D3C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D3D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0222 }, { 0x1D3E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D3F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D40, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D41, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D42, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D43, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D44, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0250 }, { 0x1D45, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0251 }, { 0x1D46, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D02 }, { 0x1D47, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D48, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D49, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D4A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0259 }, { 0x1D4B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x025B }, { 0x1D4C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x025C }, { 0x1D4D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D4F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D50, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D51, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x014B }, { 0x1D52, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D53, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0254 }, { 0x1D54, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D16 }, { 0x1D55, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D17 }, { 0x1D56, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D57, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D58, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D59, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D1D }, { 0x1D5A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x026F }, { 0x1D5B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D5C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D25 }, { 0x1D5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D5E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B4 }, { 0x1D60, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D61, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D62, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D63, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D64, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D65, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D66, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D67, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D68, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x1D69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D6A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D78, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043D }, { 0x1D9B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0252 }, { 0x1D9C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D9D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0255 }, { 0x1D9E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00F0 }, { 0x1D9F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x025C }, { 0x1DA0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1DA1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x025F }, { 0x1DA2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0261 }, { 0x1DA3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0265 }, { 0x1DA4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0268 }, { 0x1DA5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0269 }, { 0x1DA6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x026A }, { 0x1DA7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D7B }, { 0x1DA8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x029D }, { 0x1DA9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x026D }, { 0x1DAA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D85 }, { 0x1DAB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x029F }, { 0x1DAC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0271 }, { 0x1DAD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0270 }, { 0x1DAE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0272 }, { 0x1DAF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0273 }, { 0x1DB0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0274 }, { 0x1DB1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0275 }, { 0x1DB2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0278 }, { 0x1DB3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0282 }, { 0x1DB4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0283 }, { 0x1DB5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x01AB }, { 0x1DB6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0289 }, { 0x1DB7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x028A }, { 0x1DB8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D1C }, { 0x1DB9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x028B }, { 0x1DBA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x028C }, { 0x1DBB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1DBC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0290 }, { 0x1DBD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0291 }, { 0x1DBE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0292 }, { 0x1DBF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x1DC0, 230, 0, 0 }, { 0x1DC1, 230, 0, 0 }, { 0x1DC2, 220, 0, 0 }, { 0x1DC3, 230, 0, 0 }, { 0x1DC4, 230, 0, 0 }, { 0x1DC5, 230, 0, 0 }, { 0x1DC6, 230, 0, 0 }, { 0x1DC7, 230, 0, 0 }, { 0x1DC8, 230, 0, 0 }, { 0x1DC9, 230, 0, 0 }, { 0x1DCA, 220, 0, 0 }, { 0x1DCB, 230, 0, 0 }, { 0x1DCC, 230, 0, 0 }, { 0x1DCD, 234, 0, 0 }, { 0x1DCE, 214, 0, 0 }, { 0x1DCF, 220, 0, 0 }, { 0x1DD0, 202, 0, 0 }, { 0x1DD1, 230, 0, 0 }, { 0x1DD2, 230, 0, 0 }, { 0x1DD3, 230, 0, 0 }, { 0x1DD4, 230, 0, 0 }, { 0x1DD5, 230, 0, 0 }, { 0x1DD6, 230, 0, 0 }, { 0x1DD7, 230, 0, 0 }, { 0x1DD8, 230, 0, 0 }, { 0x1DD9, 230, 0, 0 }, { 0x1DDA, 230, 0, 0 }, { 0x1DDB, 230, 0, 0 }, { 0x1DDC, 230, 0, 0 }, { 0x1DDD, 230, 0, 0 }, { 0x1DDE, 230, 0, 0 }, { 0x1DDF, 230, 0, 0 }, { 0x1DE0, 230, 0, 0 }, { 0x1DE1, 230, 0, 0 }, { 0x1DE2, 230, 0, 0 }, { 0x1DE3, 230, 0, 0 }, { 0x1DE4, 230, 0, 0 }, { 0x1DE5, 230, 0, 0 }, { 0x1DE6, 230, 0, 0 }, { 0x1DE7, 230, 0, 0 }, { 0x1DE8, 230, 0, 0 }, { 0x1DE9, 230, 0, 0 }, { 0x1DEA, 230, 0, 0 }, { 0x1DEB, 230, 0, 0 }, { 0x1DEC, 230, 0, 0 }, { 0x1DED, 230, 0, 0 }, { 0x1DEE, 230, 0, 0 }, { 0x1DEF, 230, 0, 0 }, { 0x1DF0, 230, 0, 0 }, { 0x1DF1, 230, 0, 0 }, { 0x1DF2, 230, 0, 0 }, { 0x1DF3, 230, 0, 0 }, { 0x1DF4, 230, 0, 0 }, { 0x1DF5, 230, 0, 0 }, { 0x1DF6, 232, 0, 0 }, { 0x1DF7, 228, 0, 0 }, { 0x1DF8, 228, 0, 0 }, { 0x1DF9, 220, 0, 0 }, { 0x1DFA, 218, 0, 0 }, { 0x1DFB, 230, 0, 0 }, { 0x1DFC, 233, 0, 0 }, { 0x1DFD, 220, 0, 0 }, { 0x1DFE, 230, 0, 0 }, { 0x1DFF, 220, 0, 0 }, { 0x1E00, 0, 2, 907 }, { 0x1E01, 0, 2, 909 }, { 0x1E02, 0, 2, 911 }, { 0x1E03, 0, 2, 913 }, { 0x1E04, 0, 2, 915 }, { 0x1E05, 0, 2, 917 }, { 0x1E06, 0, 2, 919 }, { 0x1E07, 0, 2, 921 }, { 0x1E08, 0, 2, 923 }, { 0x1E09, 0, 2, 925 }, { 0x1E0A, 0, 2, 927 }, { 0x1E0B, 0, 2, 929 }, { 0x1E0C, 0, 2, 931 }, { 0x1E0D, 0, 2, 933 }, { 0x1E0E, 0, 2, 935 }, { 0x1E0F, 0, 2, 937 }, { 0x1E10, 0, 2, 939 }, { 0x1E11, 0, 2, 941 }, { 0x1E12, 0, 2, 943 }, { 0x1E13, 0, 2, 945 }, { 0x1E14, 0, 2, 947 }, { 0x1E15, 0, 2, 949 }, { 0x1E16, 0, 2, 951 }, { 0x1E17, 0, 2, 953 }, { 0x1E18, 0, 2, 955 }, { 0x1E19, 0, 2, 957 }, { 0x1E1A, 0, 2, 959 }, { 0x1E1B, 0, 2, 961 }, { 0x1E1C, 0, 2, 963 }, { 0x1E1D, 0, 2, 965 }, { 0x1E1E, 0, 2, 967 }, { 0x1E1F, 0, 2, 969 }, { 0x1E20, 0, 2, 971 }, { 0x1E21, 0, 2, 973 }, { 0x1E22, 0, 2, 975 }, { 0x1E23, 0, 2, 977 }, { 0x1E24, 0, 2, 979 }, { 0x1E25, 0, 2, 981 }, { 0x1E26, 0, 2, 983 }, { 0x1E27, 0, 2, 985 }, { 0x1E28, 0, 2, 987 }, { 0x1E29, 0, 2, 989 }, { 0x1E2A, 0, 2, 991 }, { 0x1E2B, 0, 2, 993 }, { 0x1E2C, 0, 2, 995 }, { 0x1E2D, 0, 2, 997 }, { 0x1E2E, 0, 2, 999 }, { 0x1E2F, 0, 2, 1001 }, { 0x1E30, 0, 2, 1003 }, { 0x1E31, 0, 2, 1005 }, { 0x1E32, 0, 2, 1007 }, { 0x1E33, 0, 2, 1009 }, { 0x1E34, 0, 2, 1011 }, { 0x1E35, 0, 2, 1013 }, { 0x1E36, 0, 2, 1015 }, { 0x1E37, 0, 2, 1017 }, { 0x1E38, 0, 2, 1019 }, { 0x1E39, 0, 2, 1021 }, { 0x1E3A, 0, 2, 1023 }, { 0x1E3B, 0, 2, 1025 }, { 0x1E3C, 0, 2, 1027 }, { 0x1E3D, 0, 2, 1029 }, { 0x1E3E, 0, 2, 1031 }, { 0x1E3F, 0, 2, 1033 }, { 0x1E40, 0, 2, 1035 }, { 0x1E41, 0, 2, 1037 }, { 0x1E42, 0, 2, 1039 }, { 0x1E43, 0, 2, 1041 }, { 0x1E44, 0, 2, 1043 }, { 0x1E45, 0, 2, 1045 }, { 0x1E46, 0, 2, 1047 }, { 0x1E47, 0, 2, 1049 }, { 0x1E48, 0, 2, 1051 }, { 0x1E49, 0, 2, 1053 }, { 0x1E4A, 0, 2, 1055 }, { 0x1E4B, 0, 2, 1057 }, { 0x1E4C, 0, 2, 1059 }, { 0x1E4D, 0, 2, 1061 }, { 0x1E4E, 0, 2, 1063 }, { 0x1E4F, 0, 2, 1065 }, { 0x1E50, 0, 2, 1067 }, { 0x1E51, 0, 2, 1069 }, { 0x1E52, 0, 2, 1071 }, { 0x1E53, 0, 2, 1073 }, { 0x1E54, 0, 2, 1075 }, { 0x1E55, 0, 2, 1077 }, { 0x1E56, 0, 2, 1079 }, { 0x1E57, 0, 2, 1081 }, { 0x1E58, 0, 2, 1083 }, { 0x1E59, 0, 2, 1085 }, { 0x1E5A, 0, 2, 1087 }, { 0x1E5B, 0, 2, 1089 }, { 0x1E5C, 0, 2, 1091 }, { 0x1E5D, 0, 2, 1093 }, { 0x1E5E, 0, 2, 1095 }, { 0x1E5F, 0, 2, 1097 }, { 0x1E60, 0, 2, 1099 }, { 0x1E61, 0, 2, 1101 }, { 0x1E62, 0, 2, 1103 }, { 0x1E63, 0, 2, 1105 }, { 0x1E64, 0, 2, 1107 }, { 0x1E65, 0, 2, 1109 }, { 0x1E66, 0, 2, 1111 }, { 0x1E67, 0, 2, 1113 }, { 0x1E68, 0, 2, 1115 }, { 0x1E69, 0, 2, 1117 }, { 0x1E6A, 0, 2, 1119 }, { 0x1E6B, 0, 2, 1121 }, { 0x1E6C, 0, 2, 1123 }, { 0x1E6D, 0, 2, 1125 }, { 0x1E6E, 0, 2, 1127 }, { 0x1E6F, 0, 2, 1129 }, { 0x1E70, 0, 2, 1131 }, { 0x1E71, 0, 2, 1133 }, { 0x1E72, 0, 2, 1135 }, { 0x1E73, 0, 2, 1137 }, { 0x1E74, 0, 2, 1139 }, { 0x1E75, 0, 2, 1141 }, { 0x1E76, 0, 2, 1143 }, { 0x1E77, 0, 2, 1145 }, { 0x1E78, 0, 2, 1147 }, { 0x1E79, 0, 2, 1149 }, { 0x1E7A, 0, 2, 1151 }, { 0x1E7B, 0, 2, 1153 }, { 0x1E7C, 0, 2, 1155 }, { 0x1E7D, 0, 2, 1157 }, { 0x1E7E, 0, 2, 1159 }, { 0x1E7F, 0, 2, 1161 }, { 0x1E80, 0, 2, 1163 }, { 0x1E81, 0, 2, 1165 }, { 0x1E82, 0, 2, 1167 }, { 0x1E83, 0, 2, 1169 }, { 0x1E84, 0, 2, 1171 }, { 0x1E85, 0, 2, 1173 }, { 0x1E86, 0, 2, 1175 }, { 0x1E87, 0, 2, 1177 }, { 0x1E88, 0, 2, 1179 }, { 0x1E89, 0, 2, 1181 }, { 0x1E8A, 0, 2, 1183 }, { 0x1E8B, 0, 2, 1185 }, { 0x1E8C, 0, 2, 1187 }, { 0x1E8D, 0, 2, 1189 }, { 0x1E8E, 0, 2, 1191 }, { 0x1E8F, 0, 2, 1193 }, { 0x1E90, 0, 2, 1195 }, { 0x1E91, 0, 2, 1197 }, { 0x1E92, 0, 2, 1199 }, { 0x1E93, 0, 2, 1201 }, { 0x1E94, 0, 2, 1203 }, { 0x1E95, 0, 2, 1205 }, { 0x1E96, 0, 2, 1207 }, { 0x1E97, 0, 2, 1209 }, { 0x1E98, 0, 2, 1211 }, { 0x1E99, 0, 2, 1213 }, { 0x1E9A, 0, 2 | DECOMP_COMPAT, 1215 }, { 0x1E9B, 0, 2, 1217 }, { 0x1EA0, 0, 2, 1219 }, { 0x1EA1, 0, 2, 1221 }, { 0x1EA2, 0, 2, 1223 }, { 0x1EA3, 0, 2, 1225 }, { 0x1EA4, 0, 2, 1227 }, { 0x1EA5, 0, 2, 1229 }, { 0x1EA6, 0, 2, 1231 }, { 0x1EA7, 0, 2, 1233 }, { 0x1EA8, 0, 2, 1235 }, { 0x1EA9, 0, 2, 1237 }, { 0x1EAA, 0, 2, 1239 }, { 0x1EAB, 0, 2, 1241 }, { 0x1EAC, 0, 2, 1243 }, { 0x1EAD, 0, 2, 1245 }, { 0x1EAE, 0, 2, 1247 }, { 0x1EAF, 0, 2, 1249 }, { 0x1EB0, 0, 2, 1251 }, { 0x1EB1, 0, 2, 1253 }, { 0x1EB2, 0, 2, 1255 }, { 0x1EB3, 0, 2, 1257 }, { 0x1EB4, 0, 2, 1259 }, { 0x1EB5, 0, 2, 1261 }, { 0x1EB6, 0, 2, 1263 }, { 0x1EB7, 0, 2, 1265 }, { 0x1EB8, 0, 2, 1267 }, { 0x1EB9, 0, 2, 1269 }, { 0x1EBA, 0, 2, 1271 }, { 0x1EBB, 0, 2, 1273 }, { 0x1EBC, 0, 2, 1275 }, { 0x1EBD, 0, 2, 1277 }, { 0x1EBE, 0, 2, 1279 }, { 0x1EBF, 0, 2, 1281 }, { 0x1EC0, 0, 2, 1283 }, { 0x1EC1, 0, 2, 1285 }, { 0x1EC2, 0, 2, 1287 }, { 0x1EC3, 0, 2, 1289 }, { 0x1EC4, 0, 2, 1291 }, { 0x1EC5, 0, 2, 1293 }, { 0x1EC6, 0, 2, 1295 }, { 0x1EC7, 0, 2, 1297 }, { 0x1EC8, 0, 2, 1299 }, { 0x1EC9, 0, 2, 1301 }, { 0x1ECA, 0, 2, 1303 }, { 0x1ECB, 0, 2, 1305 }, { 0x1ECC, 0, 2, 1307 }, { 0x1ECD, 0, 2, 1309 }, { 0x1ECE, 0, 2, 1311 }, { 0x1ECF, 0, 2, 1313 }, { 0x1ED0, 0, 2, 1315 }, { 0x1ED1, 0, 2, 1317 }, { 0x1ED2, 0, 2, 1319 }, { 0x1ED3, 0, 2, 1321 }, { 0x1ED4, 0, 2, 1323 }, { 0x1ED5, 0, 2, 1325 }, { 0x1ED6, 0, 2, 1327 }, { 0x1ED7, 0, 2, 1329 }, { 0x1ED8, 0, 2, 1331 }, { 0x1ED9, 0, 2, 1333 }, { 0x1EDA, 0, 2, 1335 }, { 0x1EDB, 0, 2, 1337 }, { 0x1EDC, 0, 2, 1339 }, { 0x1EDD, 0, 2, 1341 }, { 0x1EDE, 0, 2, 1343 }, { 0x1EDF, 0, 2, 1345 }, { 0x1EE0, 0, 2, 1347 }, { 0x1EE1, 0, 2, 1349 }, { 0x1EE2, 0, 2, 1351 }, { 0x1EE3, 0, 2, 1353 }, { 0x1EE4, 0, 2, 1355 }, { 0x1EE5, 0, 2, 1357 }, { 0x1EE6, 0, 2, 1359 }, { 0x1EE7, 0, 2, 1361 }, { 0x1EE8, 0, 2, 1363 }, { 0x1EE9, 0, 2, 1365 }, { 0x1EEA, 0, 2, 1367 }, { 0x1EEB, 0, 2, 1369 }, { 0x1EEC, 0, 2, 1371 }, { 0x1EED, 0, 2, 1373 }, { 0x1EEE, 0, 2, 1375 }, { 0x1EEF, 0, 2, 1377 }, { 0x1EF0, 0, 2, 1379 }, { 0x1EF1, 0, 2, 1381 }, { 0x1EF2, 0, 2, 1383 }, { 0x1EF3, 0, 2, 1385 }, { 0x1EF4, 0, 2, 1387 }, { 0x1EF5, 0, 2, 1389 }, { 0x1EF6, 0, 2, 1391 }, { 0x1EF7, 0, 2, 1393 }, { 0x1EF8, 0, 2, 1395 }, { 0x1EF9, 0, 2, 1397 }, { 0x1F00, 0, 2, 1399 }, { 0x1F01, 0, 2, 1401 }, { 0x1F02, 0, 2, 1403 }, { 0x1F03, 0, 2, 1405 }, { 0x1F04, 0, 2, 1407 }, { 0x1F05, 0, 2, 1409 }, { 0x1F06, 0, 2, 1411 }, { 0x1F07, 0, 2, 1413 }, { 0x1F08, 0, 2, 1415 }, { 0x1F09, 0, 2, 1417 }, { 0x1F0A, 0, 2, 1419 }, { 0x1F0B, 0, 2, 1421 }, { 0x1F0C, 0, 2, 1423 }, { 0x1F0D, 0, 2, 1425 }, { 0x1F0E, 0, 2, 1427 }, { 0x1F0F, 0, 2, 1429 }, { 0x1F10, 0, 2, 1431 }, { 0x1F11, 0, 2, 1433 }, { 0x1F12, 0, 2, 1435 }, { 0x1F13, 0, 2, 1437 }, { 0x1F14, 0, 2, 1439 }, { 0x1F15, 0, 2, 1441 }, { 0x1F18, 0, 2, 1443 }, { 0x1F19, 0, 2, 1445 }, { 0x1F1A, 0, 2, 1447 }, { 0x1F1B, 0, 2, 1449 }, { 0x1F1C, 0, 2, 1451 }, { 0x1F1D, 0, 2, 1453 }, { 0x1F20, 0, 2, 1455 }, { 0x1F21, 0, 2, 1457 }, { 0x1F22, 0, 2, 1459 }, { 0x1F23, 0, 2, 1461 }, { 0x1F24, 0, 2, 1463 }, { 0x1F25, 0, 2, 1465 }, { 0x1F26, 0, 2, 1467 }, { 0x1F27, 0, 2, 1469 }, { 0x1F28, 0, 2, 1471 }, { 0x1F29, 0, 2, 1473 }, { 0x1F2A, 0, 2, 1475 }, { 0x1F2B, 0, 2, 1477 }, { 0x1F2C, 0, 2, 1479 }, { 0x1F2D, 0, 2, 1481 }, { 0x1F2E, 0, 2, 1483 }, { 0x1F2F, 0, 2, 1485 }, { 0x1F30, 0, 2, 1487 }, { 0x1F31, 0, 2, 1489 }, { 0x1F32, 0, 2, 1491 }, { 0x1F33, 0, 2, 1493 }, { 0x1F34, 0, 2, 1495 }, { 0x1F35, 0, 2, 1497 }, { 0x1F36, 0, 2, 1499 }, { 0x1F37, 0, 2, 1501 }, { 0x1F38, 0, 2, 1503 }, { 0x1F39, 0, 2, 1505 }, { 0x1F3A, 0, 2, 1507 }, { 0x1F3B, 0, 2, 1509 }, { 0x1F3C, 0, 2, 1511 }, { 0x1F3D, 0, 2, 1513 }, { 0x1F3E, 0, 2, 1515 }, { 0x1F3F, 0, 2, 1517 }, { 0x1F40, 0, 2, 1519 }, { 0x1F41, 0, 2, 1521 }, { 0x1F42, 0, 2, 1523 }, { 0x1F43, 0, 2, 1525 }, { 0x1F44, 0, 2, 1527 }, { 0x1F45, 0, 2, 1529 }, { 0x1F48, 0, 2, 1531 }, { 0x1F49, 0, 2, 1533 }, { 0x1F4A, 0, 2, 1535 }, { 0x1F4B, 0, 2, 1537 }, { 0x1F4C, 0, 2, 1539 }, { 0x1F4D, 0, 2, 1541 }, { 0x1F50, 0, 2, 1543 }, { 0x1F51, 0, 2, 1545 }, { 0x1F52, 0, 2, 1547 }, { 0x1F53, 0, 2, 1549 }, { 0x1F54, 0, 2, 1551 }, { 0x1F55, 0, 2, 1553 }, { 0x1F56, 0, 2, 1555 }, { 0x1F57, 0, 2, 1557 }, { 0x1F59, 0, 2, 1559 }, { 0x1F5B, 0, 2, 1561 }, { 0x1F5D, 0, 2, 1563 }, { 0x1F5F, 0, 2, 1565 }, { 0x1F60, 0, 2, 1567 }, { 0x1F61, 0, 2, 1569 }, { 0x1F62, 0, 2, 1571 }, { 0x1F63, 0, 2, 1573 }, { 0x1F64, 0, 2, 1575 }, { 0x1F65, 0, 2, 1577 }, { 0x1F66, 0, 2, 1579 }, { 0x1F67, 0, 2, 1581 }, { 0x1F68, 0, 2, 1583 }, { 0x1F69, 0, 2, 1585 }, { 0x1F6A, 0, 2, 1587 }, { 0x1F6B, 0, 2, 1589 }, { 0x1F6C, 0, 2, 1591 }, { 0x1F6D, 0, 2, 1593 }, { 0x1F6E, 0, 2, 1595 }, { 0x1F6F, 0, 2, 1597 }, { 0x1F70, 0, 2, 1599 }, { 0x1F71, 0, 1 | DECOMP_INLINE, 0x03AC }, { 0x1F72, 0, 2, 1601 }, { 0x1F73, 0, 1 | DECOMP_INLINE, 0x03AD }, { 0x1F74, 0, 2, 1603 }, { 0x1F75, 0, 1 | DECOMP_INLINE, 0x03AE }, { 0x1F76, 0, 2, 1605 }, { 0x1F77, 0, 1 | DECOMP_INLINE, 0x03AF }, { 0x1F78, 0, 2, 1607 }, { 0x1F79, 0, 1 | DECOMP_INLINE, 0x03CC }, { 0x1F7A, 0, 2, 1609 }, { 0x1F7B, 0, 1 | DECOMP_INLINE, 0x03CD }, { 0x1F7C, 0, 2, 1611 }, { 0x1F7D, 0, 1 | DECOMP_INLINE, 0x03CE }, { 0x1F80, 0, 2, 1613 }, { 0x1F81, 0, 2, 1615 }, { 0x1F82, 0, 2, 1617 }, { 0x1F83, 0, 2, 1619 }, { 0x1F84, 0, 2, 1621 }, { 0x1F85, 0, 2, 1623 }, { 0x1F86, 0, 2, 1625 }, { 0x1F87, 0, 2, 1627 }, { 0x1F88, 0, 2, 1629 }, { 0x1F89, 0, 2, 1631 }, { 0x1F8A, 0, 2, 1633 }, { 0x1F8B, 0, 2, 1635 }, { 0x1F8C, 0, 2, 1637 }, { 0x1F8D, 0, 2, 1639 }, { 0x1F8E, 0, 2, 1641 }, { 0x1F8F, 0, 2, 1643 }, { 0x1F90, 0, 2, 1645 }, { 0x1F91, 0, 2, 1647 }, { 0x1F92, 0, 2, 1649 }, { 0x1F93, 0, 2, 1651 }, { 0x1F94, 0, 2, 1653 }, { 0x1F95, 0, 2, 1655 }, { 0x1F96, 0, 2, 1657 }, { 0x1F97, 0, 2, 1659 }, { 0x1F98, 0, 2, 1661 }, { 0x1F99, 0, 2, 1663 }, { 0x1F9A, 0, 2, 1665 }, { 0x1F9B, 0, 2, 1667 }, { 0x1F9C, 0, 2, 1669 }, { 0x1F9D, 0, 2, 1671 }, { 0x1F9E, 0, 2, 1673 }, { 0x1F9F, 0, 2, 1675 }, { 0x1FA0, 0, 2, 1677 }, { 0x1FA1, 0, 2, 1679 }, { 0x1FA2, 0, 2, 1681 }, { 0x1FA3, 0, 2, 1683 }, { 0x1FA4, 0, 2, 1685 }, { 0x1FA5, 0, 2, 1687 }, { 0x1FA6, 0, 2, 1689 }, { 0x1FA7, 0, 2, 1691 }, { 0x1FA8, 0, 2, 1693 }, { 0x1FA9, 0, 2, 1695 }, { 0x1FAA, 0, 2, 1697 }, { 0x1FAB, 0, 2, 1699 }, { 0x1FAC, 0, 2, 1701 }, { 0x1FAD, 0, 2, 1703 }, { 0x1FAE, 0, 2, 1705 }, { 0x1FAF, 0, 2, 1707 }, { 0x1FB0, 0, 2, 1709 }, { 0x1FB1, 0, 2, 1711 }, { 0x1FB2, 0, 2, 1713 }, { 0x1FB3, 0, 2, 1715 }, { 0x1FB4, 0, 2, 1717 }, { 0x1FB6, 0, 2, 1719 }, { 0x1FB7, 0, 2, 1721 }, { 0x1FB8, 0, 2, 1723 }, { 0x1FB9, 0, 2, 1725 }, { 0x1FBA, 0, 2, 1727 }, { 0x1FBB, 0, 1 | DECOMP_INLINE, 0x0386 }, { 0x1FBC, 0, 2, 1729 }, { 0x1FBD, 0, 2 | DECOMP_COMPAT, 1731 }, { 0x1FBE, 0, 1 | DECOMP_INLINE, 0x03B9 }, { 0x1FBF, 0, 2 | DECOMP_COMPAT, 1733 }, { 0x1FC0, 0, 2 | DECOMP_COMPAT, 1735 }, { 0x1FC1, 0, 2, 1737 }, { 0x1FC2, 0, 2, 1739 }, { 0x1FC3, 0, 2, 1741 }, { 0x1FC4, 0, 2, 1743 }, { 0x1FC6, 0, 2, 1745 }, { 0x1FC7, 0, 2, 1747 }, { 0x1FC8, 0, 2, 1749 }, { 0x1FC9, 0, 1 | DECOMP_INLINE, 0x0388 }, { 0x1FCA, 0, 2, 1751 }, { 0x1FCB, 0, 1 | DECOMP_INLINE, 0x0389 }, { 0x1FCC, 0, 2, 1753 }, { 0x1FCD, 0, 2, 1755 }, { 0x1FCE, 0, 2, 1757 }, { 0x1FCF, 0, 2, 1759 }, { 0x1FD0, 0, 2, 1761 }, { 0x1FD1, 0, 2, 1763 }, { 0x1FD2, 0, 2, 1765 }, { 0x1FD3, 0, 1 | DECOMP_INLINE, 0x0390 }, { 0x1FD6, 0, 2, 1767 }, { 0x1FD7, 0, 2, 1769 }, { 0x1FD8, 0, 2, 1771 }, { 0x1FD9, 0, 2, 1773 }, { 0x1FDA, 0, 2, 1775 }, { 0x1FDB, 0, 1 | DECOMP_INLINE, 0x038A }, { 0x1FDD, 0, 2, 1777 }, { 0x1FDE, 0, 2, 1779 }, { 0x1FDF, 0, 2, 1781 }, { 0x1FE0, 0, 2, 1783 }, { 0x1FE1, 0, 2, 1785 }, { 0x1FE2, 0, 2, 1787 }, { 0x1FE3, 0, 1 | DECOMP_INLINE, 0x03B0 }, { 0x1FE4, 0, 2, 1789 }, { 0x1FE5, 0, 2, 1791 }, { 0x1FE6, 0, 2, 1793 }, { 0x1FE7, 0, 2, 1795 }, { 0x1FE8, 0, 2, 1797 }, { 0x1FE9, 0, 2, 1799 }, { 0x1FEA, 0, 2, 1801 }, { 0x1FEB, 0, 1 | DECOMP_INLINE, 0x038E }, { 0x1FEC, 0, 2, 1803 }, { 0x1FED, 0, 2, 1805 }, { 0x1FEE, 0, 1 | DECOMP_INLINE, 0x0385 }, { 0x1FEF, 0, 1 | DECOMP_INLINE, 0x0060 }, { 0x1FF2, 0, 2, 1807 }, { 0x1FF3, 0, 2, 1809 }, { 0x1FF4, 0, 2, 1811 }, { 0x1FF6, 0, 2, 1813 }, { 0x1FF7, 0, 2, 1815 }, { 0x1FF8, 0, 2, 1817 }, { 0x1FF9, 0, 1 | DECOMP_INLINE, 0x038C }, { 0x1FFA, 0, 2, 1819 }, { 0x1FFB, 0, 1 | DECOMP_INLINE, 0x038F }, { 0x1FFC, 0, 2, 1821 }, { 0x1FFD, 0, 1 | DECOMP_INLINE, 0x00B4 }, { 0x1FFE, 0, 2 | DECOMP_COMPAT, 1823 }, { 0x2000, 0, 1 | DECOMP_INLINE, 0x2002 }, { 0x2001, 0, 1 | DECOMP_INLINE, 0x2003 }, { 0x2002, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2003, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2004, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2005, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2006, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2007, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2008, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2009, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x200A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2011, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2010 }, { 0x2017, 0, 2 | DECOMP_COMPAT, 1825 }, { 0x2024, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002E }, { 0x2025, 0, 2 | DECOMP_COMPAT, 1827 }, { 0x2026, 0, 3 | DECOMP_COMPAT, 1829 }, { 0x202F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2033, 0, 2 | DECOMP_COMPAT, 1832 }, { 0x2034, 0, 3 | DECOMP_COMPAT, 1834 }, { 0x2036, 0, 2 | DECOMP_COMPAT, 1837 }, { 0x2037, 0, 3 | DECOMP_COMPAT, 1839 }, { 0x203C, 0, 2 | DECOMP_COMPAT, 1842 }, { 0x203E, 0, 2 | DECOMP_COMPAT, 1844 }, { 0x2047, 0, 2 | DECOMP_COMPAT, 1846 }, { 0x2048, 0, 2 | DECOMP_COMPAT, 1848 }, { 0x2049, 0, 2 | DECOMP_COMPAT, 1850 }, { 0x2057, 0, 4 | DECOMP_COMPAT, 1852 }, { 0x205F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x2070, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x2071, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x2074, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x2075, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x2076, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x2077, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x2078, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x2079, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x207A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002B }, { 0x207B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2212 }, { 0x207C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003D }, { 0x207D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0028 }, { 0x207E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0029 }, { 0x207F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x2080, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x2081, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x2082, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x2083, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x2084, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x2085, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x2086, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x2087, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x2088, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x2089, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x208A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002B }, { 0x208B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2212 }, { 0x208C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003D }, { 0x208D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0028 }, { 0x208E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0029 }, { 0x2090, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x2091, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x2092, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x2093, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x2094, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0259 }, { 0x2095, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x2096, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x2097, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x2098, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x2099, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x209A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x209B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x209C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x20A8, 0, 2 | DECOMP_COMPAT, 1856 }, { 0x20D0, 230, 0, 0 }, { 0x20D1, 230, 0, 0 }, { 0x20D2, 1, 0, 0 }, { 0x20D3, 1, 0, 0 }, { 0x20D4, 230, 0, 0 }, { 0x20D5, 230, 0, 0 }, { 0x20D6, 230, 0, 0 }, { 0x20D7, 230, 0, 0 }, { 0x20D8, 1, 0, 0 }, { 0x20D9, 1, 0, 0 }, { 0x20DA, 1, 0, 0 }, { 0x20DB, 230, 0, 0 }, { 0x20DC, 230, 0, 0 }, { 0x20E1, 230, 0, 0 }, { 0x20E5, 1, 0, 0 }, { 0x20E6, 1, 0, 0 }, { 0x20E7, 230, 0, 0 }, { 0x20E8, 220, 0, 0 }, { 0x20E9, 230, 0, 0 }, { 0x20EA, 1, 0, 0 }, { 0x20EB, 1, 0, 0 }, { 0x20EC, 220, 0, 0 }, { 0x20ED, 220, 0, 0 }, { 0x20EE, 220, 0, 0 }, { 0x20EF, 220, 0, 0 }, { 0x20F0, 230, 0, 0 }, { 0x2100, 0, 3 | DECOMP_COMPAT, 1858 }, { 0x2101, 0, 3 | DECOMP_COMPAT, 1861 }, { 0x2102, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x2103, 0, 2 | DECOMP_COMPAT, 1864 }, { 0x2105, 0, 3 | DECOMP_COMPAT, 1866 }, { 0x2106, 0, 3 | DECOMP_COMPAT, 1869 }, { 0x2107, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0190 }, { 0x2109, 0, 2 | DECOMP_COMPAT, 1872 }, { 0x210A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x210B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x210C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x210D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x210E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x210F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0127 }, { 0x2110, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x2111, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x2112, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x2113, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x2115, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x2116, 0, 2 | DECOMP_COMPAT, 1874 }, { 0x2119, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x211A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x211B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x211C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x211D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x2120, 0, 2 | DECOMP_COMPAT, 1876 }, { 0x2121, 0, 3 | DECOMP_COMPAT, 1878 }, { 0x2122, 0, 2 | DECOMP_COMPAT, 1881 }, { 0x2124, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x2126, 0, 1 | DECOMP_INLINE, 0x03A9 }, { 0x2128, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x212A, 0, 1 | DECOMP_INLINE, 0x004B }, { 0x212B, 0, 1 | DECOMP_INLINE, 0x00C5 }, { 0x212C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x212D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x212F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x2130, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x2131, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x2133, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x2134, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x2135, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D0 }, { 0x2136, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D1 }, { 0x2137, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D2 }, { 0x2138, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D3 }, { 0x2139, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x213B, 0, 3 | DECOMP_COMPAT, 1883 }, { 0x213C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x213D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x213E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0393 }, { 0x213F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A0 }, { 0x2140, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2211 }, { 0x2145, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x2146, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x2147, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x2148, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x2149, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x2150, 0, 3 | DECOMP_COMPAT, 1886 }, { 0x2151, 0, 3 | DECOMP_COMPAT, 1889 }, { 0x2152, 0, 4 | DECOMP_COMPAT, 1892 }, { 0x2153, 0, 3 | DECOMP_COMPAT, 1896 }, { 0x2154, 0, 3 | DECOMP_COMPAT, 1899 }, { 0x2155, 0, 3 | DECOMP_COMPAT, 1902 }, { 0x2156, 0, 3 | DECOMP_COMPAT, 1905 }, { 0x2157, 0, 3 | DECOMP_COMPAT, 1908 }, { 0x2158, 0, 3 | DECOMP_COMPAT, 1911 }, { 0x2159, 0, 3 | DECOMP_COMPAT, 1914 }, { 0x215A, 0, 3 | DECOMP_COMPAT, 1917 }, { 0x215B, 0, 3 | DECOMP_COMPAT, 1920 }, { 0x215C, 0, 3 | DECOMP_COMPAT, 1923 }, { 0x215D, 0, 3 | DECOMP_COMPAT, 1926 }, { 0x215E, 0, 3 | DECOMP_COMPAT, 1929 }, { 0x215F, 0, 2 | DECOMP_COMPAT, 1932 }, { 0x2160, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x2161, 0, 2 | DECOMP_COMPAT, 1934 }, { 0x2162, 0, 3 | DECOMP_COMPAT, 1936 }, { 0x2163, 0, 2 | DECOMP_COMPAT, 1939 }, { 0x2164, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x2165, 0, 2 | DECOMP_COMPAT, 1941 }, { 0x2166, 0, 3 | DECOMP_COMPAT, 1943 }, { 0x2167, 0, 4 | DECOMP_COMPAT, 1946 }, { 0x2168, 0, 2 | DECOMP_COMPAT, 1950 }, { 0x2169, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x216A, 0, 2 | DECOMP_COMPAT, 1952 }, { 0x216B, 0, 3 | DECOMP_COMPAT, 1954 }, { 0x216C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x216D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x216E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x216F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x2170, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x2171, 0, 2 | DECOMP_COMPAT, 1957 }, { 0x2172, 0, 3 | DECOMP_COMPAT, 1959 }, { 0x2173, 0, 2 | DECOMP_COMPAT, 1962 }, { 0x2174, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x2175, 0, 2 | DECOMP_COMPAT, 1964 }, { 0x2176, 0, 3 | DECOMP_COMPAT, 1966 }, { 0x2177, 0, 4 | DECOMP_COMPAT, 1969 }, { 0x2178, 0, 2 | DECOMP_COMPAT, 1973 }, { 0x2179, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x217A, 0, 2 | DECOMP_COMPAT, 1975 }, { 0x217B, 0, 3 | DECOMP_COMPAT, 1977 }, { 0x217C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x217D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x217E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x217F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x2189, 0, 3 | DECOMP_COMPAT, 1980 }, { 0x219A, 0, 2, 1983 }, { 0x219B, 0, 2, 1985 }, { 0x21AE, 0, 2, 1987 }, { 0x21CD, 0, 2, 1989 }, { 0x21CE, 0, 2, 1991 }, { 0x21CF, 0, 2, 1993 }, { 0x2204, 0, 2, 1995 }, { 0x2209, 0, 2, 1997 }, { 0x220C, 0, 2, 1999 }, { 0x2224, 0, 2, 2001 }, { 0x2226, 0, 2, 2003 }, { 0x222C, 0, 2 | DECOMP_COMPAT, 2005 }, { 0x222D, 0, 3 | DECOMP_COMPAT, 2007 }, { 0x222F, 0, 2 | DECOMP_COMPAT, 2010 }, { 0x2230, 0, 3 | DECOMP_COMPAT, 2012 }, { 0x2241, 0, 2, 2015 }, { 0x2244, 0, 2, 2017 }, { 0x2247, 0, 2, 2019 }, { 0x2249, 0, 2, 2021 }, { 0x2260, 0, 2, 2023 }, { 0x2262, 0, 2, 2025 }, { 0x226D, 0, 2, 2027 }, { 0x226E, 0, 2, 2029 }, { 0x226F, 0, 2, 2031 }, { 0x2270, 0, 2, 2033 }, { 0x2271, 0, 2, 2035 }, { 0x2274, 0, 2, 2037 }, { 0x2275, 0, 2, 2039 }, { 0x2278, 0, 2, 2041 }, { 0x2279, 0, 2, 2043 }, { 0x2280, 0, 2, 2045 }, { 0x2281, 0, 2, 2047 }, { 0x2284, 0, 2, 2049 }, { 0x2285, 0, 2, 2051 }, { 0x2288, 0, 2, 2053 }, { 0x2289, 0, 2, 2055 }, { 0x22AC, 0, 2, 2057 }, { 0x22AD, 0, 2, 2059 }, { 0x22AE, 0, 2, 2061 }, { 0x22AF, 0, 2, 2063 }, { 0x22E0, 0, 2, 2065 }, { 0x22E1, 0, 2, 2067 }, { 0x22E2, 0, 2, 2069 }, { 0x22E3, 0, 2, 2071 }, { 0x22EA, 0, 2, 2073 }, { 0x22EB, 0, 2, 2075 }, { 0x22EC, 0, 2, 2077 }, { 0x22ED, 0, 2, 2079 }, { 0x2329, 0, 1 | DECOMP_INLINE, 0x3008 }, { 0x232A, 0, 1 | DECOMP_INLINE, 0x3009 }, { 0x2460, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x2461, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x2462, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x2463, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x2464, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x2465, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x2466, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x2467, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x2468, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x2469, 0, 2 | DECOMP_COMPAT, 2081 }, { 0x246A, 0, 2 | DECOMP_COMPAT, 2083 }, { 0x246B, 0, 2 | DECOMP_COMPAT, 2085 }, { 0x246C, 0, 2 | DECOMP_COMPAT, 2087 }, { 0x246D, 0, 2 | DECOMP_COMPAT, 2089 }, { 0x246E, 0, 2 | DECOMP_COMPAT, 2091 }, { 0x246F, 0, 2 | DECOMP_COMPAT, 2093 }, { 0x2470, 0, 2 | DECOMP_COMPAT, 2095 }, { 0x2471, 0, 2 | DECOMP_COMPAT, 2097 }, { 0x2472, 0, 2 | DECOMP_COMPAT, 2099 }, { 0x2473, 0, 2 | DECOMP_COMPAT, 2101 }, { 0x2474, 0, 3 | DECOMP_COMPAT, 2103 }, { 0x2475, 0, 3 | DECOMP_COMPAT, 2106 }, { 0x2476, 0, 3 | DECOMP_COMPAT, 2109 }, { 0x2477, 0, 3 | DECOMP_COMPAT, 2112 }, { 0x2478, 0, 3 | DECOMP_COMPAT, 2115 }, { 0x2479, 0, 3 | DECOMP_COMPAT, 2118 }, { 0x247A, 0, 3 | DECOMP_COMPAT, 2121 }, { 0x247B, 0, 3 | DECOMP_COMPAT, 2124 }, { 0x247C, 0, 3 | DECOMP_COMPAT, 2127 }, { 0x247D, 0, 4 | DECOMP_COMPAT, 2130 }, { 0x247E, 0, 4 | DECOMP_COMPAT, 2134 }, { 0x247F, 0, 4 | DECOMP_COMPAT, 2138 }, { 0x2480, 0, 4 | DECOMP_COMPAT, 2142 }, { 0x2481, 0, 4 | DECOMP_COMPAT, 2146 }, { 0x2482, 0, 4 | DECOMP_COMPAT, 2150 }, { 0x2483, 0, 4 | DECOMP_COMPAT, 2154 }, { 0x2484, 0, 4 | DECOMP_COMPAT, 2158 }, { 0x2485, 0, 4 | DECOMP_COMPAT, 2162 }, { 0x2486, 0, 4 | DECOMP_COMPAT, 2166 }, { 0x2487, 0, 4 | DECOMP_COMPAT, 2170 }, { 0x2488, 0, 2 | DECOMP_COMPAT, 2174 }, { 0x2489, 0, 2 | DECOMP_COMPAT, 2176 }, { 0x248A, 0, 2 | DECOMP_COMPAT, 2178 }, { 0x248B, 0, 2 | DECOMP_COMPAT, 2180 }, { 0x248C, 0, 2 | DECOMP_COMPAT, 2182 }, { 0x248D, 0, 2 | DECOMP_COMPAT, 2184 }, { 0x248E, 0, 2 | DECOMP_COMPAT, 2186 }, { 0x248F, 0, 2 | DECOMP_COMPAT, 2188 }, { 0x2490, 0, 2 | DECOMP_COMPAT, 2190 }, { 0x2491, 0, 3 | DECOMP_COMPAT, 2192 }, { 0x2492, 0, 3 | DECOMP_COMPAT, 2195 }, { 0x2493, 0, 3 | DECOMP_COMPAT, 2198 }, { 0x2494, 0, 3 | DECOMP_COMPAT, 2201 }, { 0x2495, 0, 3 | DECOMP_COMPAT, 2204 }, { 0x2496, 0, 3 | DECOMP_COMPAT, 2207 }, { 0x2497, 0, 3 | DECOMP_COMPAT, 2210 }, { 0x2498, 0, 3 | DECOMP_COMPAT, 2213 }, { 0x2499, 0, 3 | DECOMP_COMPAT, 2216 }, { 0x249A, 0, 3 | DECOMP_COMPAT, 2219 }, { 0x249B, 0, 3 | DECOMP_COMPAT, 2222 }, { 0x249C, 0, 3 | DECOMP_COMPAT, 2225 }, { 0x249D, 0, 3 | DECOMP_COMPAT, 2228 }, { 0x249E, 0, 3 | DECOMP_COMPAT, 2231 }, { 0x249F, 0, 3 | DECOMP_COMPAT, 2234 }, { 0x24A0, 0, 3 | DECOMP_COMPAT, 2237 }, { 0x24A1, 0, 3 | DECOMP_COMPAT, 2240 }, { 0x24A2, 0, 3 | DECOMP_COMPAT, 2243 }, { 0x24A3, 0, 3 | DECOMP_COMPAT, 2246 }, { 0x24A4, 0, 3 | DECOMP_COMPAT, 2249 }, { 0x24A5, 0, 3 | DECOMP_COMPAT, 2252 }, { 0x24A6, 0, 3 | DECOMP_COMPAT, 2255 }, { 0x24A7, 0, 3 | DECOMP_COMPAT, 2258 }, { 0x24A8, 0, 3 | DECOMP_COMPAT, 2261 }, { 0x24A9, 0, 3 | DECOMP_COMPAT, 2264 }, { 0x24AA, 0, 3 | DECOMP_COMPAT, 2267 }, { 0x24AB, 0, 3 | DECOMP_COMPAT, 2270 }, { 0x24AC, 0, 3 | DECOMP_COMPAT, 2273 }, { 0x24AD, 0, 3 | DECOMP_COMPAT, 2276 }, { 0x24AE, 0, 3 | DECOMP_COMPAT, 2279 }, { 0x24AF, 0, 3 | DECOMP_COMPAT, 2282 }, { 0x24B0, 0, 3 | DECOMP_COMPAT, 2285 }, { 0x24B1, 0, 3 | DECOMP_COMPAT, 2288 }, { 0x24B2, 0, 3 | DECOMP_COMPAT, 2291 }, { 0x24B3, 0, 3 | DECOMP_COMPAT, 2294 }, { 0x24B4, 0, 3 | DECOMP_COMPAT, 2297 }, { 0x24B5, 0, 3 | DECOMP_COMPAT, 2300 }, { 0x24B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x24B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x24B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x24B9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x24BA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x24BB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x24BC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x24BD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x24BE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x24BF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x24C0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x24C1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x24C2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x24C3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x24C4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x24C5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x24C6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x24C7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x24C8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x24C9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x24CA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x24CB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x24CC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x24CD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x24CE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x24CF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x24D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x24D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x24D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x24D3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x24D4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x24D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x24D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x24D7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x24D8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x24D9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x24DA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x24DB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x24DC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x24DD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x24DE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x24DF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x24E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x24E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x24E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x24E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x24E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x24E5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x24E6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x24E7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x24E8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x24E9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x24EA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x2A0C, 0, 4 | DECOMP_COMPAT, 2303 }, { 0x2A74, 0, 3 | DECOMP_COMPAT, 2307 }, { 0x2A75, 0, 2 | DECOMP_COMPAT, 2310 }, { 0x2A76, 0, 3 | DECOMP_COMPAT, 2312 }, { 0x2ADC, 0, 2 | DECOMP_NO_COMPOSE, 2315 }, /* in exclusion list */ { 0x2C7C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x2C7D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x2CEF, 230, 0, 0 }, { 0x2CF0, 230, 0, 0 }, { 0x2CF1, 230, 0, 0 }, { 0x2D6F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2D61 }, { 0x2D7F, 9, 0, 0 }, { 0x2DE0, 230, 0, 0 }, { 0x2DE1, 230, 0, 0 }, { 0x2DE2, 230, 0, 0 }, { 0x2DE3, 230, 0, 0 }, { 0x2DE4, 230, 0, 0 }, { 0x2DE5, 230, 0, 0 }, { 0x2DE6, 230, 0, 0 }, { 0x2DE7, 230, 0, 0 }, { 0x2DE8, 230, 0, 0 }, { 0x2DE9, 230, 0, 0 }, { 0x2DEA, 230, 0, 0 }, { 0x2DEB, 230, 0, 0 }, { 0x2DEC, 230, 0, 0 }, { 0x2DED, 230, 0, 0 }, { 0x2DEE, 230, 0, 0 }, { 0x2DEF, 230, 0, 0 }, { 0x2DF0, 230, 0, 0 }, { 0x2DF1, 230, 0, 0 }, { 0x2DF2, 230, 0, 0 }, { 0x2DF3, 230, 0, 0 }, { 0x2DF4, 230, 0, 0 }, { 0x2DF5, 230, 0, 0 }, { 0x2DF6, 230, 0, 0 }, { 0x2DF7, 230, 0, 0 }, { 0x2DF8, 230, 0, 0 }, { 0x2DF9, 230, 0, 0 }, { 0x2DFA, 230, 0, 0 }, { 0x2DFB, 230, 0, 0 }, { 0x2DFC, 230, 0, 0 }, { 0x2DFD, 230, 0, 0 }, { 0x2DFE, 230, 0, 0 }, { 0x2DFF, 230, 0, 0 }, { 0x2E9F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6BCD }, { 0x2EF3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F9F }, { 0x2F00, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E00 }, { 0x2F01, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E28 }, { 0x2F02, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E36 }, { 0x2F03, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E3F }, { 0x2F04, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E59 }, { 0x2F05, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E85 }, { 0x2F06, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E8C }, { 0x2F07, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4EA0 }, { 0x2F08, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4EBA }, { 0x2F09, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x513F }, { 0x2F0A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5165 }, { 0x2F0B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x516B }, { 0x2F0C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5182 }, { 0x2F0D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5196 }, { 0x2F0E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x51AB }, { 0x2F0F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x51E0 }, { 0x2F10, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x51F5 }, { 0x2F11, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5200 }, { 0x2F12, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x529B }, { 0x2F13, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x52F9 }, { 0x2F14, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5315 }, { 0x2F15, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x531A }, { 0x2F16, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5338 }, { 0x2F17, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5341 }, { 0x2F18, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x535C }, { 0x2F19, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5369 }, { 0x2F1A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5382 }, { 0x2F1B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53B6 }, { 0x2F1C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53C8 }, { 0x2F1D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53E3 }, { 0x2F1E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x56D7 }, { 0x2F1F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x571F }, { 0x2F20, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x58EB }, { 0x2F21, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5902 }, { 0x2F22, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x590A }, { 0x2F23, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5915 }, { 0x2F24, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5927 }, { 0x2F25, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5973 }, { 0x2F26, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5B50 }, { 0x2F27, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5B80 }, { 0x2F28, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5BF8 }, { 0x2F29, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5C0F }, { 0x2F2A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5C22 }, { 0x2F2B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5C38 }, { 0x2F2C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5C6E }, { 0x2F2D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5C71 }, { 0x2F2E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5DDB }, { 0x2F2F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5DE5 }, { 0x2F30, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5DF1 }, { 0x2F31, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5DFE }, { 0x2F32, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5E72 }, { 0x2F33, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5E7A }, { 0x2F34, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5E7F }, { 0x2F35, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5EF4 }, { 0x2F36, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5EFE }, { 0x2F37, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F0B }, { 0x2F38, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F13 }, { 0x2F39, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F50 }, { 0x2F3A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F61 }, { 0x2F3B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F73 }, { 0x2F3C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5FC3 }, { 0x2F3D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6208 }, { 0x2F3E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6236 }, { 0x2F3F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x624B }, { 0x2F40, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x652F }, { 0x2F41, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6534 }, { 0x2F42, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6587 }, { 0x2F43, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6597 }, { 0x2F44, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x65A4 }, { 0x2F45, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x65B9 }, { 0x2F46, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x65E0 }, { 0x2F47, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x65E5 }, { 0x2F48, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x66F0 }, { 0x2F49, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6708 }, { 0x2F4A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6728 }, { 0x2F4B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6B20 }, { 0x2F4C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6B62 }, { 0x2F4D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6B79 }, { 0x2F4E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6BB3 }, { 0x2F4F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6BCB }, { 0x2F50, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6BD4 }, { 0x2F51, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6BDB }, { 0x2F52, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6C0F }, { 0x2F53, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6C14 }, { 0x2F54, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6C34 }, { 0x2F55, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x706B }, { 0x2F56, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x722A }, { 0x2F57, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7236 }, { 0x2F58, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x723B }, { 0x2F59, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x723F }, { 0x2F5A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7247 }, { 0x2F5B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7259 }, { 0x2F5C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x725B }, { 0x2F5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x72AC }, { 0x2F5E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7384 }, { 0x2F5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7389 }, { 0x2F60, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x74DC }, { 0x2F61, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x74E6 }, { 0x2F62, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7518 }, { 0x2F63, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x751F }, { 0x2F64, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7528 }, { 0x2F65, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7530 }, { 0x2F66, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x758B }, { 0x2F67, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7592 }, { 0x2F68, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7676 }, { 0x2F69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x767D }, { 0x2F6A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x76AE }, { 0x2F6B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x76BF }, { 0x2F6C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x76EE }, { 0x2F6D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x77DB }, { 0x2F6E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x77E2 }, { 0x2F6F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x77F3 }, { 0x2F70, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x793A }, { 0x2F71, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x79B8 }, { 0x2F72, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x79BE }, { 0x2F73, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7A74 }, { 0x2F74, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7ACB }, { 0x2F75, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7AF9 }, { 0x2F76, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7C73 }, { 0x2F77, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7CF8 }, { 0x2F78, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7F36 }, { 0x2F79, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7F51 }, { 0x2F7A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7F8A }, { 0x2F7B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7FBD }, { 0x2F7C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8001 }, { 0x2F7D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x800C }, { 0x2F7E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8012 }, { 0x2F7F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8033 }, { 0x2F80, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x807F }, { 0x2F81, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8089 }, { 0x2F82, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x81E3 }, { 0x2F83, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x81EA }, { 0x2F84, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x81F3 }, { 0x2F85, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x81FC }, { 0x2F86, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x820C }, { 0x2F87, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x821B }, { 0x2F88, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x821F }, { 0x2F89, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x826E }, { 0x2F8A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8272 }, { 0x2F8B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8278 }, { 0x2F8C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x864D }, { 0x2F8D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x866B }, { 0x2F8E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8840 }, { 0x2F8F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x884C }, { 0x2F90, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8863 }, { 0x2F91, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x897E }, { 0x2F92, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x898B }, { 0x2F93, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x89D2 }, { 0x2F94, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8A00 }, { 0x2F95, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8C37 }, { 0x2F96, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8C46 }, { 0x2F97, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8C55 }, { 0x2F98, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8C78 }, { 0x2F99, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8C9D }, { 0x2F9A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8D64 }, { 0x2F9B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8D70 }, { 0x2F9C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8DB3 }, { 0x2F9D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8EAB }, { 0x2F9E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8ECA }, { 0x2F9F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8F9B }, { 0x2FA0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8FB0 }, { 0x2FA1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8FB5 }, { 0x2FA2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9091 }, { 0x2FA3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9149 }, { 0x2FA4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x91C6 }, { 0x2FA5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x91CC }, { 0x2FA6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x91D1 }, { 0x2FA7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9577 }, { 0x2FA8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9580 }, { 0x2FA9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x961C }, { 0x2FAA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x96B6 }, { 0x2FAB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x96B9 }, { 0x2FAC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x96E8 }, { 0x2FAD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9751 }, { 0x2FAE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x975E }, { 0x2FAF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9762 }, { 0x2FB0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9769 }, { 0x2FB1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x97CB }, { 0x2FB2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x97ED }, { 0x2FB3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x97F3 }, { 0x2FB4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9801 }, { 0x2FB5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x98A8 }, { 0x2FB6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x98DB }, { 0x2FB7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x98DF }, { 0x2FB8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9996 }, { 0x2FB9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9999 }, { 0x2FBA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x99AC }, { 0x2FBB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9AA8 }, { 0x2FBC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9AD8 }, { 0x2FBD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9ADF }, { 0x2FBE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9B25 }, { 0x2FBF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9B2F }, { 0x2FC0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9B32 }, { 0x2FC1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9B3C }, { 0x2FC2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9B5A }, { 0x2FC3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9CE5 }, { 0x2FC4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9E75 }, { 0x2FC5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9E7F }, { 0x2FC6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9EA5 }, { 0x2FC7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9EBB }, { 0x2FC8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9EC3 }, { 0x2FC9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9ECD }, { 0x2FCA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9ED1 }, { 0x2FCB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9EF9 }, { 0x2FCC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9EFD }, { 0x2FCD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F0E }, { 0x2FCE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F13 }, { 0x2FCF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F20 }, { 0x2FD0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F3B }, { 0x2FD1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F4A }, { 0x2FD2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F52 }, { 0x2FD3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F8D }, { 0x2FD4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9F9C }, { 0x2FD5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9FA0 }, { 0x3000, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0020 }, { 0x302A, 218, 0, 0 }, { 0x302B, 228, 0, 0 }, { 0x302C, 232, 0, 0 }, { 0x302D, 222, 0, 0 }, { 0x302E, 224, 0, 0 }, { 0x302F, 224, 0, 0 }, { 0x3036, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3012 }, { 0x3038, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5341 }, { 0x3039, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5344 }, { 0x303A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5345 }, { 0x304C, 0, 2, 2317 }, { 0x304E, 0, 2, 2319 }, { 0x3050, 0, 2, 2321 }, { 0x3052, 0, 2, 2323 }, { 0x3054, 0, 2, 2325 }, { 0x3056, 0, 2, 2327 }, { 0x3058, 0, 2, 2329 }, { 0x305A, 0, 2, 2331 }, { 0x305C, 0, 2, 2333 }, { 0x305E, 0, 2, 2335 }, { 0x3060, 0, 2, 2337 }, { 0x3062, 0, 2, 2339 }, { 0x3065, 0, 2, 2341 }, { 0x3067, 0, 2, 2343 }, { 0x3069, 0, 2, 2345 }, { 0x3070, 0, 2, 2347 }, { 0x3071, 0, 2, 2349 }, { 0x3073, 0, 2, 2351 }, { 0x3074, 0, 2, 2353 }, { 0x3076, 0, 2, 2355 }, { 0x3077, 0, 2, 2357 }, { 0x3079, 0, 2, 2359 }, { 0x307A, 0, 2, 2361 }, { 0x307C, 0, 2, 2363 }, { 0x307D, 0, 2, 2365 }, { 0x3094, 0, 2, 2367 }, { 0x3099, 8, 0, 0 }, { 0x309A, 8, 0, 0 }, { 0x309B, 0, 2 | DECOMP_COMPAT, 2369 }, { 0x309C, 0, 2 | DECOMP_COMPAT, 2371 }, { 0x309E, 0, 2, 2373 }, { 0x309F, 0, 2 | DECOMP_COMPAT, 2375 }, { 0x30AC, 0, 2, 2377 }, { 0x30AE, 0, 2, 2379 }, { 0x30B0, 0, 2, 2381 }, { 0x30B2, 0, 2, 2383 }, { 0x30B4, 0, 2, 2385 }, { 0x30B6, 0, 2, 2387 }, { 0x30B8, 0, 2, 2389 }, { 0x30BA, 0, 2, 2391 }, { 0x30BC, 0, 2, 2393 }, { 0x30BE, 0, 2, 2395 }, { 0x30C0, 0, 2, 2397 }, { 0x30C2, 0, 2, 2399 }, { 0x30C5, 0, 2, 2401 }, { 0x30C7, 0, 2, 2403 }, { 0x30C9, 0, 2, 2405 }, { 0x30D0, 0, 2, 2407 }, { 0x30D1, 0, 2, 2409 }, { 0x30D3, 0, 2, 2411 }, { 0x30D4, 0, 2, 2413 }, { 0x30D6, 0, 2, 2415 }, { 0x30D7, 0, 2, 2417 }, { 0x30D9, 0, 2, 2419 }, { 0x30DA, 0, 2, 2421 }, { 0x30DC, 0, 2, 2423 }, { 0x30DD, 0, 2, 2425 }, { 0x30F4, 0, 2, 2427 }, { 0x30F7, 0, 2, 2429 }, { 0x30F8, 0, 2, 2431 }, { 0x30F9, 0, 2, 2433 }, { 0x30FA, 0, 2, 2435 }, { 0x30FE, 0, 2, 2437 }, { 0x30FF, 0, 2 | DECOMP_COMPAT, 2439 }, { 0x3131, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1100 }, { 0x3132, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1101 }, { 0x3133, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11AA }, { 0x3134, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1102 }, { 0x3135, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11AC }, { 0x3136, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11AD }, { 0x3137, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1103 }, { 0x3138, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1104 }, { 0x3139, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1105 }, { 0x313A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11B0 }, { 0x313B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11B1 }, { 0x313C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11B2 }, { 0x313D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11B3 }, { 0x313E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11B4 }, { 0x313F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11B5 }, { 0x3140, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x111A }, { 0x3141, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1106 }, { 0x3142, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1107 }, { 0x3143, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1108 }, { 0x3144, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1121 }, { 0x3145, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1109 }, { 0x3146, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110A }, { 0x3147, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110B }, { 0x3148, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110C }, { 0x3149, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110D }, { 0x314A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110E }, { 0x314B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110F }, { 0x314C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1110 }, { 0x314D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1111 }, { 0x314E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1112 }, { 0x314F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1161 }, { 0x3150, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1162 }, { 0x3151, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1163 }, { 0x3152, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1164 }, { 0x3153, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1165 }, { 0x3154, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1166 }, { 0x3155, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1167 }, { 0x3156, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1168 }, { 0x3157, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1169 }, { 0x3158, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x116A }, { 0x3159, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x116B }, { 0x315A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x116C }, { 0x315B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x116D }, { 0x315C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x116E }, { 0x315D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x116F }, { 0x315E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1170 }, { 0x315F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1171 }, { 0x3160, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1172 }, { 0x3161, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1173 }, { 0x3162, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1174 }, { 0x3163, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1175 }, { 0x3164, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1160 }, { 0x3165, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1114 }, { 0x3166, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1115 }, { 0x3167, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11C7 }, { 0x3168, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11C8 }, { 0x3169, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11CC }, { 0x316A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11CE }, { 0x316B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11D3 }, { 0x316C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11D7 }, { 0x316D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11D9 }, { 0x316E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x111C }, { 0x316F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11DD }, { 0x3170, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11DF }, { 0x3171, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x111D }, { 0x3172, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x111E }, { 0x3173, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1120 }, { 0x3174, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1122 }, { 0x3175, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1123 }, { 0x3176, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1127 }, { 0x3177, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1129 }, { 0x3178, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x112B }, { 0x3179, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x112C }, { 0x317A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x112D }, { 0x317B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x112E }, { 0x317C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x112F }, { 0x317D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1132 }, { 0x317E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1136 }, { 0x317F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1140 }, { 0x3180, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1147 }, { 0x3181, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x114C }, { 0x3182, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11F1 }, { 0x3183, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11F2 }, { 0x3184, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1157 }, { 0x3185, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1158 }, { 0x3186, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1159 }, { 0x3187, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1184 }, { 0x3188, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1185 }, { 0x3189, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1188 }, { 0x318A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1191 }, { 0x318B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1192 }, { 0x318C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1194 }, { 0x318D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x119E }, { 0x318E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x11A1 }, { 0x3192, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E00 }, { 0x3193, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E8C }, { 0x3194, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E09 }, { 0x3195, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x56DB }, { 0x3196, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E0A }, { 0x3197, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E2D }, { 0x3198, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E0B }, { 0x3199, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7532 }, { 0x319A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E59 }, { 0x319B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E19 }, { 0x319C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E01 }, { 0x319D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5929 }, { 0x319E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5730 }, { 0x319F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4EBA }, { 0x3200, 0, 3 | DECOMP_COMPAT, 2441 }, { 0x3201, 0, 3 | DECOMP_COMPAT, 2444 }, { 0x3202, 0, 3 | DECOMP_COMPAT, 2447 }, { 0x3203, 0, 3 | DECOMP_COMPAT, 2450 }, { 0x3204, 0, 3 | DECOMP_COMPAT, 2453 }, { 0x3205, 0, 3 | DECOMP_COMPAT, 2456 }, { 0x3206, 0, 3 | DECOMP_COMPAT, 2459 }, { 0x3207, 0, 3 | DECOMP_COMPAT, 2462 }, { 0x3208, 0, 3 | DECOMP_COMPAT, 2465 }, { 0x3209, 0, 3 | DECOMP_COMPAT, 2468 }, { 0x320A, 0, 3 | DECOMP_COMPAT, 2471 }, { 0x320B, 0, 3 | DECOMP_COMPAT, 2474 }, { 0x320C, 0, 3 | DECOMP_COMPAT, 2477 }, { 0x320D, 0, 3 | DECOMP_COMPAT, 2480 }, { 0x320E, 0, 4 | DECOMP_COMPAT, 2483 }, { 0x320F, 0, 4 | DECOMP_COMPAT, 2487 }, { 0x3210, 0, 4 | DECOMP_COMPAT, 2491 }, { 0x3211, 0, 4 | DECOMP_COMPAT, 2495 }, { 0x3212, 0, 4 | DECOMP_COMPAT, 2499 }, { 0x3213, 0, 4 | DECOMP_COMPAT, 2503 }, { 0x3214, 0, 4 | DECOMP_COMPAT, 2507 }, { 0x3215, 0, 4 | DECOMP_COMPAT, 2511 }, { 0x3216, 0, 4 | DECOMP_COMPAT, 2515 }, { 0x3217, 0, 4 | DECOMP_COMPAT, 2519 }, { 0x3218, 0, 4 | DECOMP_COMPAT, 2523 }, { 0x3219, 0, 4 | DECOMP_COMPAT, 2527 }, { 0x321A, 0, 4 | DECOMP_COMPAT, 2531 }, { 0x321B, 0, 4 | DECOMP_COMPAT, 2535 }, { 0x321C, 0, 4 | DECOMP_COMPAT, 2539 }, { 0x321D, 0, 7 | DECOMP_COMPAT, 2543 }, { 0x321E, 0, 6 | DECOMP_COMPAT, 2550 }, { 0x3220, 0, 3 | DECOMP_COMPAT, 2556 }, { 0x3221, 0, 3 | DECOMP_COMPAT, 2559 }, { 0x3222, 0, 3 | DECOMP_COMPAT, 2562 }, { 0x3223, 0, 3 | DECOMP_COMPAT, 2565 }, { 0x3224, 0, 3 | DECOMP_COMPAT, 2568 }, { 0x3225, 0, 3 | DECOMP_COMPAT, 2571 }, { 0x3226, 0, 3 | DECOMP_COMPAT, 2574 }, { 0x3227, 0, 3 | DECOMP_COMPAT, 2577 }, { 0x3228, 0, 3 | DECOMP_COMPAT, 2580 }, { 0x3229, 0, 3 | DECOMP_COMPAT, 2583 }, { 0x322A, 0, 3 | DECOMP_COMPAT, 2586 }, { 0x322B, 0, 3 | DECOMP_COMPAT, 2589 }, { 0x322C, 0, 3 | DECOMP_COMPAT, 2592 }, { 0x322D, 0, 3 | DECOMP_COMPAT, 2595 }, { 0x322E, 0, 3 | DECOMP_COMPAT, 2598 }, { 0x322F, 0, 3 | DECOMP_COMPAT, 2601 }, { 0x3230, 0, 3 | DECOMP_COMPAT, 2604 }, { 0x3231, 0, 3 | DECOMP_COMPAT, 2607 }, { 0x3232, 0, 3 | DECOMP_COMPAT, 2610 }, { 0x3233, 0, 3 | DECOMP_COMPAT, 2613 }, { 0x3234, 0, 3 | DECOMP_COMPAT, 2616 }, { 0x3235, 0, 3 | DECOMP_COMPAT, 2619 }, { 0x3236, 0, 3 | DECOMP_COMPAT, 2622 }, { 0x3237, 0, 3 | DECOMP_COMPAT, 2625 }, { 0x3238, 0, 3 | DECOMP_COMPAT, 2628 }, { 0x3239, 0, 3 | DECOMP_COMPAT, 2631 }, { 0x323A, 0, 3 | DECOMP_COMPAT, 2634 }, { 0x323B, 0, 3 | DECOMP_COMPAT, 2637 }, { 0x323C, 0, 3 | DECOMP_COMPAT, 2640 }, { 0x323D, 0, 3 | DECOMP_COMPAT, 2643 }, { 0x323E, 0, 3 | DECOMP_COMPAT, 2646 }, { 0x323F, 0, 3 | DECOMP_COMPAT, 2649 }, { 0x3240, 0, 3 | DECOMP_COMPAT, 2652 }, { 0x3241, 0, 3 | DECOMP_COMPAT, 2655 }, { 0x3242, 0, 3 | DECOMP_COMPAT, 2658 }, { 0x3243, 0, 3 | DECOMP_COMPAT, 2661 }, { 0x3244, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x554F }, { 0x3245, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5E7C }, { 0x3246, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6587 }, { 0x3247, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7B8F }, { 0x3250, 0, 3 | DECOMP_COMPAT, 2664 }, { 0x3251, 0, 2 | DECOMP_COMPAT, 2667 }, { 0x3252, 0, 2 | DECOMP_COMPAT, 2669 }, { 0x3253, 0, 2 | DECOMP_COMPAT, 2671 }, { 0x3254, 0, 2 | DECOMP_COMPAT, 2673 }, { 0x3255, 0, 2 | DECOMP_COMPAT, 2675 }, { 0x3256, 0, 2 | DECOMP_COMPAT, 2677 }, { 0x3257, 0, 2 | DECOMP_COMPAT, 2679 }, { 0x3258, 0, 2 | DECOMP_COMPAT, 2681 }, { 0x3259, 0, 2 | DECOMP_COMPAT, 2683 }, { 0x325A, 0, 2 | DECOMP_COMPAT, 2685 }, { 0x325B, 0, 2 | DECOMP_COMPAT, 2687 }, { 0x325C, 0, 2 | DECOMP_COMPAT, 2689 }, { 0x325D, 0, 2 | DECOMP_COMPAT, 2691 }, { 0x325E, 0, 2 | DECOMP_COMPAT, 2693 }, { 0x325F, 0, 2 | DECOMP_COMPAT, 2695 }, { 0x3260, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1100 }, { 0x3261, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1102 }, { 0x3262, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1103 }, { 0x3263, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1105 }, { 0x3264, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1106 }, { 0x3265, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1107 }, { 0x3266, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1109 }, { 0x3267, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110B }, { 0x3268, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110C }, { 0x3269, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110E }, { 0x326A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x110F }, { 0x326B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1110 }, { 0x326C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1111 }, { 0x326D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1112 }, { 0x326E, 0, 2 | DECOMP_COMPAT, 2697 }, { 0x326F, 0, 2 | DECOMP_COMPAT, 2699 }, { 0x3270, 0, 2 | DECOMP_COMPAT, 2701 }, { 0x3271, 0, 2 | DECOMP_COMPAT, 2703 }, { 0x3272, 0, 2 | DECOMP_COMPAT, 2705 }, { 0x3273, 0, 2 | DECOMP_COMPAT, 2707 }, { 0x3274, 0, 2 | DECOMP_COMPAT, 2709 }, { 0x3275, 0, 2 | DECOMP_COMPAT, 2711 }, { 0x3276, 0, 2 | DECOMP_COMPAT, 2713 }, { 0x3277, 0, 2 | DECOMP_COMPAT, 2715 }, { 0x3278, 0, 2 | DECOMP_COMPAT, 2717 }, { 0x3279, 0, 2 | DECOMP_COMPAT, 2719 }, { 0x327A, 0, 2 | DECOMP_COMPAT, 2721 }, { 0x327B, 0, 2 | DECOMP_COMPAT, 2723 }, { 0x327C, 0, 5 | DECOMP_COMPAT, 2725 }, { 0x327D, 0, 4 | DECOMP_COMPAT, 2730 }, { 0x327E, 0, 2 | DECOMP_COMPAT, 2734 }, { 0x3280, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E00 }, { 0x3281, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E8C }, { 0x3282, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E09 }, { 0x3283, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x56DB }, { 0x3284, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E94 }, { 0x3285, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x516D }, { 0x3286, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E03 }, { 0x3287, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x516B }, { 0x3288, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E5D }, { 0x3289, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5341 }, { 0x328A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6708 }, { 0x328B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x706B }, { 0x328C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6C34 }, { 0x328D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6728 }, { 0x328E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x91D1 }, { 0x328F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x571F }, { 0x3290, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x65E5 }, { 0x3291, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x682A }, { 0x3292, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6709 }, { 0x3293, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x793E }, { 0x3294, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x540D }, { 0x3295, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7279 }, { 0x3296, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8CA1 }, { 0x3297, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x795D }, { 0x3298, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x52B4 }, { 0x3299, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x79D8 }, { 0x329A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7537 }, { 0x329B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5973 }, { 0x329C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9069 }, { 0x329D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x512A }, { 0x329E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5370 }, { 0x329F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6CE8 }, { 0x32A0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x9805 }, { 0x32A1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4F11 }, { 0x32A2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5199 }, { 0x32A3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6B63 }, { 0x32A4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E0A }, { 0x32A5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E2D }, { 0x32A6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E0B }, { 0x32A7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5DE6 }, { 0x32A8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53F3 }, { 0x32A9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x533B }, { 0x32AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5B97 }, { 0x32AB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5B66 }, { 0x32AC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x76E3 }, { 0x32AD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4F01 }, { 0x32AE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8CC7 }, { 0x32AF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5354 }, { 0x32B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x591C }, { 0x32B1, 0, 2 | DECOMP_COMPAT, 2736 }, { 0x32B2, 0, 2 | DECOMP_COMPAT, 2738 }, { 0x32B3, 0, 2 | DECOMP_COMPAT, 2740 }, { 0x32B4, 0, 2 | DECOMP_COMPAT, 2742 }, { 0x32B5, 0, 2 | DECOMP_COMPAT, 2744 }, { 0x32B6, 0, 2 | DECOMP_COMPAT, 2746 }, { 0x32B7, 0, 2 | DECOMP_COMPAT, 2748 }, { 0x32B8, 0, 2 | DECOMP_COMPAT, 2750 }, { 0x32B9, 0, 2 | DECOMP_COMPAT, 2752 }, { 0x32BA, 0, 2 | DECOMP_COMPAT, 2754 }, { 0x32BB, 0, 2 | DECOMP_COMPAT, 2756 }, { 0x32BC, 0, 2 | DECOMP_COMPAT, 2758 }, { 0x32BD, 0, 2 | DECOMP_COMPAT, 2760 }, { 0x32BE, 0, 2 | DECOMP_COMPAT, 2762 }, { 0x32BF, 0, 2 | DECOMP_COMPAT, 2764 }, { 0x32C0, 0, 2 | DECOMP_COMPAT, 2766 }, { 0x32C1, 0, 2 | DECOMP_COMPAT, 2768 }, { 0x32C2, 0, 2 | DECOMP_COMPAT, 2770 }, { 0x32C3, 0, 2 | DECOMP_COMPAT, 2772 }, { 0x32C4, 0, 2 | DECOMP_COMPAT, 2774 }, { 0x32C5, 0, 2 | DECOMP_COMPAT, 2776 }, { 0x32C6, 0, 2 | DECOMP_COMPAT, 2778 }, { 0x32C7, 0, 2 | DECOMP_COMPAT, 2780 }, { 0x32C8, 0, 2 | DECOMP_COMPAT, 2782 }, { 0x32C9, 0, 3 | DECOMP_COMPAT, 2784 }, { 0x32CA, 0, 3 | DECOMP_COMPAT, 2787 }, { 0x32CB, 0, 3 | DECOMP_COMPAT, 2790 }, { 0x32CC, 0, 2 | DECOMP_COMPAT, 2793 }, { 0x32CD, 0, 3 | DECOMP_COMPAT, 2795 }, { 0x32CE, 0, 2 | DECOMP_COMPAT, 2798 }, { 0x32CF, 0, 3 | DECOMP_COMPAT, 2800 }, { 0x32D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A2 }, { 0x32D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A4 }, { 0x32D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A6 }, { 0x32D3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A8 }, { 0x32D4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AA }, { 0x32D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AB }, { 0x32D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AD }, { 0x32D7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AF }, { 0x32D8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B1 }, { 0x32D9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B3 }, { 0x32DA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B5 }, { 0x32DB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B7 }, { 0x32DC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B9 }, { 0x32DD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30BB }, { 0x32DE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30BD }, { 0x32DF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30BF }, { 0x32E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C1 }, { 0x32E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C4 }, { 0x32E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C6 }, { 0x32E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C8 }, { 0x32E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CA }, { 0x32E5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CB }, { 0x32E6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CC }, { 0x32E7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CD }, { 0x32E8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CE }, { 0x32E9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CF }, { 0x32EA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30D2 }, { 0x32EB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30D5 }, { 0x32EC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30D8 }, { 0x32ED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30DB }, { 0x32EE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30DE }, { 0x32EF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30DF }, { 0x32F0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E0 }, { 0x32F1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E1 }, { 0x32F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E2 }, { 0x32F3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E4 }, { 0x32F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E6 }, { 0x32F5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E8 }, { 0x32F6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E9 }, { 0x32F7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EA }, { 0x32F8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EB }, { 0x32F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EC }, { 0x32FA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30ED }, { 0x32FB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EF }, { 0x32FC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30F0 }, { 0x32FD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30F1 }, { 0x32FE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30F2 }, { 0x32FF, 0, 2 | DECOMP_COMPAT, 2803 }, { 0x3300, 0, 4 | DECOMP_COMPAT, 2805 }, { 0x3301, 0, 4 | DECOMP_COMPAT, 2809 }, { 0x3302, 0, 4 | DECOMP_COMPAT, 2813 }, { 0x3303, 0, 3 | DECOMP_COMPAT, 2817 }, { 0x3304, 0, 4 | DECOMP_COMPAT, 2820 }, { 0x3305, 0, 3 | DECOMP_COMPAT, 2824 }, { 0x3306, 0, 3 | DECOMP_COMPAT, 2827 }, { 0x3307, 0, 5 | DECOMP_COMPAT, 2830 }, { 0x3308, 0, 4 | DECOMP_COMPAT, 2835 }, { 0x3309, 0, 3 | DECOMP_COMPAT, 2839 }, { 0x330A, 0, 3 | DECOMP_COMPAT, 2842 }, { 0x330B, 0, 3 | DECOMP_COMPAT, 2845 }, { 0x330C, 0, 4 | DECOMP_COMPAT, 2848 }, { 0x330D, 0, 4 | DECOMP_COMPAT, 2852 }, { 0x330E, 0, 3 | DECOMP_COMPAT, 2856 }, { 0x330F, 0, 3 | DECOMP_COMPAT, 2859 }, { 0x3310, 0, 2 | DECOMP_COMPAT, 2862 }, { 0x3311, 0, 3 | DECOMP_COMPAT, 2864 }, { 0x3312, 0, 4 | DECOMP_COMPAT, 2867 }, { 0x3313, 0, 4 | DECOMP_COMPAT, 2871 }, { 0x3314, 0, 2 | DECOMP_COMPAT, 2875 }, { 0x3315, 0, 5 | DECOMP_COMPAT, 2877 }, { 0x3316, 0, 6 | DECOMP_COMPAT, 2882 }, { 0x3317, 0, 5 | DECOMP_COMPAT, 2888 }, { 0x3318, 0, 3 | DECOMP_COMPAT, 2893 }, { 0x3319, 0, 5 | DECOMP_COMPAT, 2896 }, { 0x331A, 0, 5 | DECOMP_COMPAT, 2901 }, { 0x331B, 0, 4 | DECOMP_COMPAT, 2906 }, { 0x331C, 0, 3 | DECOMP_COMPAT, 2910 }, { 0x331D, 0, 3 | DECOMP_COMPAT, 2913 }, { 0x331E, 0, 3 | DECOMP_COMPAT, 2916 }, { 0x331F, 0, 4 | DECOMP_COMPAT, 2919 }, { 0x3320, 0, 5 | DECOMP_COMPAT, 2923 }, { 0x3321, 0, 4 | DECOMP_COMPAT, 2928 }, { 0x3322, 0, 3 | DECOMP_COMPAT, 2932 }, { 0x3323, 0, 3 | DECOMP_COMPAT, 2935 }, { 0x3324, 0, 3 | DECOMP_COMPAT, 2938 }, { 0x3325, 0, 2 | DECOMP_COMPAT, 2941 }, { 0x3326, 0, 2 | DECOMP_COMPAT, 2943 }, { 0x3327, 0, 2 | DECOMP_COMPAT, 2945 }, { 0x3328, 0, 2 | DECOMP_COMPAT, 2947 }, { 0x3329, 0, 3 | DECOMP_COMPAT, 2949 }, { 0x332A, 0, 3 | DECOMP_COMPAT, 2952 }, { 0x332B, 0, 5 | DECOMP_COMPAT, 2955 }, { 0x332C, 0, 3 | DECOMP_COMPAT, 2960 }, { 0x332D, 0, 4 | DECOMP_COMPAT, 2963 }, { 0x332E, 0, 5 | DECOMP_COMPAT, 2967 }, { 0x332F, 0, 3 | DECOMP_COMPAT, 2972 }, { 0x3330, 0, 2 | DECOMP_COMPAT, 2975 }, { 0x3331, 0, 2 | DECOMP_COMPAT, 2977 }, { 0x3332, 0, 5 | DECOMP_COMPAT, 2979 }, { 0x3333, 0, 4 | DECOMP_COMPAT, 2984 }, { 0x3334, 0, 5 | DECOMP_COMPAT, 2988 }, { 0x3335, 0, 3 | DECOMP_COMPAT, 2993 }, { 0x3336, 0, 5 | DECOMP_COMPAT, 2996 }, { 0x3337, 0, 2 | DECOMP_COMPAT, 3001 }, { 0x3338, 0, 3 | DECOMP_COMPAT, 3003 }, { 0x3339, 0, 3 | DECOMP_COMPAT, 3006 }, { 0x333A, 0, 3 | DECOMP_COMPAT, 3009 }, { 0x333B, 0, 3 | DECOMP_COMPAT, 3012 }, { 0x333C, 0, 3 | DECOMP_COMPAT, 3015 }, { 0x333D, 0, 4 | DECOMP_COMPAT, 3018 }, { 0x333E, 0, 3 | DECOMP_COMPAT, 3022 }, { 0x333F, 0, 2 | DECOMP_COMPAT, 3025 }, { 0x3340, 0, 3 | DECOMP_COMPAT, 3027 }, { 0x3341, 0, 3 | DECOMP_COMPAT, 3030 }, { 0x3342, 0, 3 | DECOMP_COMPAT, 3033 }, { 0x3343, 0, 4 | DECOMP_COMPAT, 3036 }, { 0x3344, 0, 3 | DECOMP_COMPAT, 3040 }, { 0x3345, 0, 3 | DECOMP_COMPAT, 3043 }, { 0x3346, 0, 3 | DECOMP_COMPAT, 3046 }, { 0x3347, 0, 5 | DECOMP_COMPAT, 3049 }, { 0x3348, 0, 4 | DECOMP_COMPAT, 3054 }, { 0x3349, 0, 2 | DECOMP_COMPAT, 3058 }, { 0x334A, 0, 5 | DECOMP_COMPAT, 3060 }, { 0x334B, 0, 2 | DECOMP_COMPAT, 3065 }, { 0x334C, 0, 4 | DECOMP_COMPAT, 3067 }, { 0x334D, 0, 4 | DECOMP_COMPAT, 3071 }, { 0x334E, 0, 3 | DECOMP_COMPAT, 3075 }, { 0x334F, 0, 3 | DECOMP_COMPAT, 3078 }, { 0x3350, 0, 3 | DECOMP_COMPAT, 3081 }, { 0x3351, 0, 4 | DECOMP_COMPAT, 3084 }, { 0x3352, 0, 2 | DECOMP_COMPAT, 3088 }, { 0x3353, 0, 3 | DECOMP_COMPAT, 3090 }, { 0x3354, 0, 4 | DECOMP_COMPAT, 3093 }, { 0x3355, 0, 2 | DECOMP_COMPAT, 3097 }, { 0x3356, 0, 5 | DECOMP_COMPAT, 3099 }, { 0x3357, 0, 3 | DECOMP_COMPAT, 3104 }, { 0x3358, 0, 2 | DECOMP_COMPAT, 3107 }, { 0x3359, 0, 2 | DECOMP_COMPAT, 3109 }, { 0x335A, 0, 2 | DECOMP_COMPAT, 3111 }, { 0x335B, 0, 2 | DECOMP_COMPAT, 3113 }, { 0x335C, 0, 2 | DECOMP_COMPAT, 3115 }, { 0x335D, 0, 2 | DECOMP_COMPAT, 3117 }, { 0x335E, 0, 2 | DECOMP_COMPAT, 3119 }, { 0x335F, 0, 2 | DECOMP_COMPAT, 3121 }, { 0x3360, 0, 2 | DECOMP_COMPAT, 3123 }, { 0x3361, 0, 2 | DECOMP_COMPAT, 3125 }, { 0x3362, 0, 3 | DECOMP_COMPAT, 3127 }, { 0x3363, 0, 3 | DECOMP_COMPAT, 3130 }, { 0x3364, 0, 3 | DECOMP_COMPAT, 3133 }, { 0x3365, 0, 3 | DECOMP_COMPAT, 3136 }, { 0x3366, 0, 3 | DECOMP_COMPAT, 3139 }, { 0x3367, 0, 3 | DECOMP_COMPAT, 3142 }, { 0x3368, 0, 3 | DECOMP_COMPAT, 3145 }, { 0x3369, 0, 3 | DECOMP_COMPAT, 3148 }, { 0x336A, 0, 3 | DECOMP_COMPAT, 3151 }, { 0x336B, 0, 3 | DECOMP_COMPAT, 3154 }, { 0x336C, 0, 3 | DECOMP_COMPAT, 3157 }, { 0x336D, 0, 3 | DECOMP_COMPAT, 3160 }, { 0x336E, 0, 3 | DECOMP_COMPAT, 3163 }, { 0x336F, 0, 3 | DECOMP_COMPAT, 3166 }, { 0x3370, 0, 3 | DECOMP_COMPAT, 3169 }, { 0x3371, 0, 3 | DECOMP_COMPAT, 3172 }, { 0x3372, 0, 2 | DECOMP_COMPAT, 3175 }, { 0x3373, 0, 2 | DECOMP_COMPAT, 3177 }, { 0x3374, 0, 3 | DECOMP_COMPAT, 3179 }, { 0x3375, 0, 2 | DECOMP_COMPAT, 3182 }, { 0x3376, 0, 2 | DECOMP_COMPAT, 3184 }, { 0x3377, 0, 2 | DECOMP_COMPAT, 3186 }, { 0x3378, 0, 3 | DECOMP_COMPAT, 3188 }, { 0x3379, 0, 3 | DECOMP_COMPAT, 3191 }, { 0x337A, 0, 2 | DECOMP_COMPAT, 3194 }, { 0x337B, 0, 2 | DECOMP_COMPAT, 3196 }, { 0x337C, 0, 2 | DECOMP_COMPAT, 3198 }, { 0x337D, 0, 2 | DECOMP_COMPAT, 3200 }, { 0x337E, 0, 2 | DECOMP_COMPAT, 3202 }, { 0x337F, 0, 4 | DECOMP_COMPAT, 3204 }, { 0x3380, 0, 2 | DECOMP_COMPAT, 3208 }, { 0x3381, 0, 2 | DECOMP_COMPAT, 3210 }, { 0x3382, 0, 2 | DECOMP_COMPAT, 3212 }, { 0x3383, 0, 2 | DECOMP_COMPAT, 3214 }, { 0x3384, 0, 2 | DECOMP_COMPAT, 3216 }, { 0x3385, 0, 2 | DECOMP_COMPAT, 3218 }, { 0x3386, 0, 2 | DECOMP_COMPAT, 3220 }, { 0x3387, 0, 2 | DECOMP_COMPAT, 3222 }, { 0x3388, 0, 3 | DECOMP_COMPAT, 3224 }, { 0x3389, 0, 4 | DECOMP_COMPAT, 3227 }, { 0x338A, 0, 2 | DECOMP_COMPAT, 3231 }, { 0x338B, 0, 2 | DECOMP_COMPAT, 3233 }, { 0x338C, 0, 2 | DECOMP_COMPAT, 3235 }, { 0x338D, 0, 2 | DECOMP_COMPAT, 3237 }, { 0x338E, 0, 2 | DECOMP_COMPAT, 3239 }, { 0x338F, 0, 2 | DECOMP_COMPAT, 3241 }, { 0x3390, 0, 2 | DECOMP_COMPAT, 3243 }, { 0x3391, 0, 3 | DECOMP_COMPAT, 3245 }, { 0x3392, 0, 3 | DECOMP_COMPAT, 3248 }, { 0x3393, 0, 3 | DECOMP_COMPAT, 3251 }, { 0x3394, 0, 3 | DECOMP_COMPAT, 3254 }, { 0x3395, 0, 2 | DECOMP_COMPAT, 3257 }, { 0x3396, 0, 2 | DECOMP_COMPAT, 3259 }, { 0x3397, 0, 2 | DECOMP_COMPAT, 3261 }, { 0x3398, 0, 2 | DECOMP_COMPAT, 3263 }, { 0x3399, 0, 2 | DECOMP_COMPAT, 3265 }, { 0x339A, 0, 2 | DECOMP_COMPAT, 3267 }, { 0x339B, 0, 2 | DECOMP_COMPAT, 3269 }, { 0x339C, 0, 2 | DECOMP_COMPAT, 3271 }, { 0x339D, 0, 2 | DECOMP_COMPAT, 3273 }, { 0x339E, 0, 2 | DECOMP_COMPAT, 3275 }, { 0x339F, 0, 3 | DECOMP_COMPAT, 3277 }, { 0x33A0, 0, 3 | DECOMP_COMPAT, 3280 }, { 0x33A1, 0, 2 | DECOMP_COMPAT, 3283 }, { 0x33A2, 0, 3 | DECOMP_COMPAT, 3285 }, { 0x33A3, 0, 3 | DECOMP_COMPAT, 3288 }, { 0x33A4, 0, 3 | DECOMP_COMPAT, 3291 }, { 0x33A5, 0, 2 | DECOMP_COMPAT, 3294 }, { 0x33A6, 0, 3 | DECOMP_COMPAT, 3296 }, { 0x33A7, 0, 3 | DECOMP_COMPAT, 3299 }, { 0x33A8, 0, 4 | DECOMP_COMPAT, 3302 }, { 0x33A9, 0, 2 | DECOMP_COMPAT, 3306 }, { 0x33AA, 0, 3 | DECOMP_COMPAT, 3308 }, { 0x33AB, 0, 3 | DECOMP_COMPAT, 3311 }, { 0x33AC, 0, 3 | DECOMP_COMPAT, 3314 }, { 0x33AD, 0, 3 | DECOMP_COMPAT, 3317 }, { 0x33AE, 0, 5 | DECOMP_COMPAT, 3320 }, { 0x33AF, 0, 6 | DECOMP_COMPAT, 3325 }, { 0x33B0, 0, 2 | DECOMP_COMPAT, 3331 }, { 0x33B1, 0, 2 | DECOMP_COMPAT, 3333 }, { 0x33B2, 0, 2 | DECOMP_COMPAT, 3335 }, { 0x33B3, 0, 2 | DECOMP_COMPAT, 3337 }, { 0x33B4, 0, 2 | DECOMP_COMPAT, 3339 }, { 0x33B5, 0, 2 | DECOMP_COMPAT, 3341 }, { 0x33B6, 0, 2 | DECOMP_COMPAT, 3343 }, { 0x33B7, 0, 2 | DECOMP_COMPAT, 3345 }, { 0x33B8, 0, 2 | DECOMP_COMPAT, 3347 }, { 0x33B9, 0, 2 | DECOMP_COMPAT, 3349 }, { 0x33BA, 0, 2 | DECOMP_COMPAT, 3351 }, { 0x33BB, 0, 2 | DECOMP_COMPAT, 3353 }, { 0x33BC, 0, 2 | DECOMP_COMPAT, 3355 }, { 0x33BD, 0, 2 | DECOMP_COMPAT, 3357 }, { 0x33BE, 0, 2 | DECOMP_COMPAT, 3359 }, { 0x33BF, 0, 2 | DECOMP_COMPAT, 3361 }, { 0x33C0, 0, 2 | DECOMP_COMPAT, 3363 }, { 0x33C1, 0, 2 | DECOMP_COMPAT, 3365 }, { 0x33C2, 0, 4 | DECOMP_COMPAT, 3367 }, { 0x33C3, 0, 2 | DECOMP_COMPAT, 3371 }, { 0x33C4, 0, 2 | DECOMP_COMPAT, 3373 }, { 0x33C5, 0, 2 | DECOMP_COMPAT, 3375 }, { 0x33C6, 0, 4 | DECOMP_COMPAT, 3377 }, { 0x33C7, 0, 3 | DECOMP_COMPAT, 3381 }, { 0x33C8, 0, 2 | DECOMP_COMPAT, 3384 }, { 0x33C9, 0, 2 | DECOMP_COMPAT, 3386 }, { 0x33CA, 0, 2 | DECOMP_COMPAT, 3388 }, { 0x33CB, 0, 2 | DECOMP_COMPAT, 3390 }, { 0x33CC, 0, 2 | DECOMP_COMPAT, 3392 }, { 0x33CD, 0, 2 | DECOMP_COMPAT, 3394 }, { 0x33CE, 0, 2 | DECOMP_COMPAT, 3396 }, { 0x33CF, 0, 2 | DECOMP_COMPAT, 3398 }, { 0x33D0, 0, 2 | DECOMP_COMPAT, 3400 }, { 0x33D1, 0, 2 | DECOMP_COMPAT, 3402 }, { 0x33D2, 0, 3 | DECOMP_COMPAT, 3404 }, { 0x33D3, 0, 2 | DECOMP_COMPAT, 3407 }, { 0x33D4, 0, 2 | DECOMP_COMPAT, 3409 }, { 0x33D5, 0, 3 | DECOMP_COMPAT, 3411 }, { 0x33D6, 0, 3 | DECOMP_COMPAT, 3414 }, { 0x33D7, 0, 2 | DECOMP_COMPAT, 3417 }, { 0x33D8, 0, 4 | DECOMP_COMPAT, 3419 }, { 0x33D9, 0, 3 | DECOMP_COMPAT, 3423 }, { 0x33DA, 0, 2 | DECOMP_COMPAT, 3426 }, { 0x33DB, 0, 2 | DECOMP_COMPAT, 3428 }, { 0x33DC, 0, 2 | DECOMP_COMPAT, 3430 }, { 0x33DD, 0, 2 | DECOMP_COMPAT, 3432 }, { 0x33DE, 0, 3 | DECOMP_COMPAT, 3434 }, { 0x33DF, 0, 3 | DECOMP_COMPAT, 3437 }, { 0x33E0, 0, 2 | DECOMP_COMPAT, 3440 }, { 0x33E1, 0, 2 | DECOMP_COMPAT, 3442 }, { 0x33E2, 0, 2 | DECOMP_COMPAT, 3444 }, { 0x33E3, 0, 2 | DECOMP_COMPAT, 3446 }, { 0x33E4, 0, 2 | DECOMP_COMPAT, 3448 }, { 0x33E5, 0, 2 | DECOMP_COMPAT, 3450 }, { 0x33E6, 0, 2 | DECOMP_COMPAT, 3452 }, { 0x33E7, 0, 2 | DECOMP_COMPAT, 3454 }, { 0x33E8, 0, 2 | DECOMP_COMPAT, 3456 }, { 0x33E9, 0, 3 | DECOMP_COMPAT, 3458 }, { 0x33EA, 0, 3 | DECOMP_COMPAT, 3461 }, { 0x33EB, 0, 3 | DECOMP_COMPAT, 3464 }, { 0x33EC, 0, 3 | DECOMP_COMPAT, 3467 }, { 0x33ED, 0, 3 | DECOMP_COMPAT, 3470 }, { 0x33EE, 0, 3 | DECOMP_COMPAT, 3473 }, { 0x33EF, 0, 3 | DECOMP_COMPAT, 3476 }, { 0x33F0, 0, 3 | DECOMP_COMPAT, 3479 }, { 0x33F1, 0, 3 | DECOMP_COMPAT, 3482 }, { 0x33F2, 0, 3 | DECOMP_COMPAT, 3485 }, { 0x33F3, 0, 3 | DECOMP_COMPAT, 3488 }, { 0x33F4, 0, 3 | DECOMP_COMPAT, 3491 }, { 0x33F5, 0, 3 | DECOMP_COMPAT, 3494 }, { 0x33F6, 0, 3 | DECOMP_COMPAT, 3497 }, { 0x33F7, 0, 3 | DECOMP_COMPAT, 3500 }, { 0x33F8, 0, 3 | DECOMP_COMPAT, 3503 }, { 0x33F9, 0, 3 | DECOMP_COMPAT, 3506 }, { 0x33FA, 0, 3 | DECOMP_COMPAT, 3509 }, { 0x33FB, 0, 3 | DECOMP_COMPAT, 3512 }, { 0x33FC, 0, 3 | DECOMP_COMPAT, 3515 }, { 0x33FD, 0, 3 | DECOMP_COMPAT, 3518 }, { 0x33FE, 0, 3 | DECOMP_COMPAT, 3521 }, { 0x33FF, 0, 3 | DECOMP_COMPAT, 3524 }, { 0xA66F, 230, 0, 0 }, { 0xA674, 230, 0, 0 }, { 0xA675, 230, 0, 0 }, { 0xA676, 230, 0, 0 }, { 0xA677, 230, 0, 0 }, { 0xA678, 230, 0, 0 }, { 0xA679, 230, 0, 0 }, { 0xA67A, 230, 0, 0 }, { 0xA67B, 230, 0, 0 }, { 0xA67C, 230, 0, 0 }, { 0xA67D, 230, 0, 0 }, { 0xA69C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044A }, { 0xA69D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044C }, { 0xA69E, 230, 0, 0 }, { 0xA69F, 230, 0, 0 }, { 0xA6F0, 230, 0, 0 }, { 0xA6F1, 230, 0, 0 }, { 0xA770, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xA76F }, { 0xA7F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0xA7F3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0xA7F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0xA7F8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0126 }, { 0xA7F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0153 }, { 0xA806, 9, 0, 0 }, { 0xA82C, 9, 0, 0 }, { 0xA8C4, 9, 0, 0 }, { 0xA8E0, 230, 0, 0 }, { 0xA8E1, 230, 0, 0 }, { 0xA8E2, 230, 0, 0 }, { 0xA8E3, 230, 0, 0 }, { 0xA8E4, 230, 0, 0 }, { 0xA8E5, 230, 0, 0 }, { 0xA8E6, 230, 0, 0 }, { 0xA8E7, 230, 0, 0 }, { 0xA8E8, 230, 0, 0 }, { 0xA8E9, 230, 0, 0 }, { 0xA8EA, 230, 0, 0 }, { 0xA8EB, 230, 0, 0 }, { 0xA8EC, 230, 0, 0 }, { 0xA8ED, 230, 0, 0 }, { 0xA8EE, 230, 0, 0 }, { 0xA8EF, 230, 0, 0 }, { 0xA8F0, 230, 0, 0 }, { 0xA8F1, 230, 0, 0 }, { 0xA92B, 220, 0, 0 }, { 0xA92C, 220, 0, 0 }, { 0xA92D, 220, 0, 0 }, { 0xA953, 9, 0, 0 }, { 0xA9B3, 7, 0, 0 }, { 0xA9C0, 9, 0, 0 }, { 0xAAB0, 230, 0, 0 }, { 0xAAB2, 230, 0, 0 }, { 0xAAB3, 230, 0, 0 }, { 0xAAB4, 220, 0, 0 }, { 0xAAB7, 230, 0, 0 }, { 0xAAB8, 230, 0, 0 }, { 0xAABE, 230, 0, 0 }, { 0xAABF, 230, 0, 0 }, { 0xAAC1, 230, 0, 0 }, { 0xAAF6, 9, 0, 0 }, { 0xAB5C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xA727 }, { 0xAB5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xAB37 }, { 0xAB5E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x026B }, { 0xAB5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xAB52 }, { 0xAB69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x028D }, { 0xABED, 9, 0, 0 }, { 0xF900, 0, 1 | DECOMP_INLINE, 0x8C48 }, { 0xF901, 0, 1 | DECOMP_INLINE, 0x66F4 }, { 0xF902, 0, 1 | DECOMP_INLINE, 0x8ECA }, { 0xF903, 0, 1 | DECOMP_INLINE, 0x8CC8 }, { 0xF904, 0, 1 | DECOMP_INLINE, 0x6ED1 }, { 0xF905, 0, 1 | DECOMP_INLINE, 0x4E32 }, { 0xF906, 0, 1 | DECOMP_INLINE, 0x53E5 }, { 0xF907, 0, 1 | DECOMP_INLINE, 0x9F9C }, { 0xF908, 0, 1 | DECOMP_INLINE, 0x9F9C }, { 0xF909, 0, 1 | DECOMP_INLINE, 0x5951 }, { 0xF90A, 0, 1 | DECOMP_INLINE, 0x91D1 }, { 0xF90B, 0, 1 | DECOMP_INLINE, 0x5587 }, { 0xF90C, 0, 1 | DECOMP_INLINE, 0x5948 }, { 0xF90D, 0, 1 | DECOMP_INLINE, 0x61F6 }, { 0xF90E, 0, 1 | DECOMP_INLINE, 0x7669 }, { 0xF90F, 0, 1 | DECOMP_INLINE, 0x7F85 }, { 0xF910, 0, 1 | DECOMP_INLINE, 0x863F }, { 0xF911, 0, 1 | DECOMP_INLINE, 0x87BA }, { 0xF912, 0, 1 | DECOMP_INLINE, 0x88F8 }, { 0xF913, 0, 1 | DECOMP_INLINE, 0x908F }, { 0xF914, 0, 1 | DECOMP_INLINE, 0x6A02 }, { 0xF915, 0, 1 | DECOMP_INLINE, 0x6D1B }, { 0xF916, 0, 1 | DECOMP_INLINE, 0x70D9 }, { 0xF917, 0, 1 | DECOMP_INLINE, 0x73DE }, { 0xF918, 0, 1 | DECOMP_INLINE, 0x843D }, { 0xF919, 0, 1 | DECOMP_INLINE, 0x916A }, { 0xF91A, 0, 1 | DECOMP_INLINE, 0x99F1 }, { 0xF91B, 0, 1 | DECOMP_INLINE, 0x4E82 }, { 0xF91C, 0, 1 | DECOMP_INLINE, 0x5375 }, { 0xF91D, 0, 1 | DECOMP_INLINE, 0x6B04 }, { 0xF91E, 0, 1 | DECOMP_INLINE, 0x721B }, { 0xF91F, 0, 1 | DECOMP_INLINE, 0x862D }, { 0xF920, 0, 1 | DECOMP_INLINE, 0x9E1E }, { 0xF921, 0, 1 | DECOMP_INLINE, 0x5D50 }, { 0xF922, 0, 1 | DECOMP_INLINE, 0x6FEB }, { 0xF923, 0, 1 | DECOMP_INLINE, 0x85CD }, { 0xF924, 0, 1 | DECOMP_INLINE, 0x8964 }, { 0xF925, 0, 1 | DECOMP_INLINE, 0x62C9 }, { 0xF926, 0, 1 | DECOMP_INLINE, 0x81D8 }, { 0xF927, 0, 1 | DECOMP_INLINE, 0x881F }, { 0xF928, 0, 1 | DECOMP_INLINE, 0x5ECA }, { 0xF929, 0, 1 | DECOMP_INLINE, 0x6717 }, { 0xF92A, 0, 1 | DECOMP_INLINE, 0x6D6A }, { 0xF92B, 0, 1 | DECOMP_INLINE, 0x72FC }, { 0xF92C, 0, 1 | DECOMP_INLINE, 0x90CE }, { 0xF92D, 0, 1 | DECOMP_INLINE, 0x4F86 }, { 0xF92E, 0, 1 | DECOMP_INLINE, 0x51B7 }, { 0xF92F, 0, 1 | DECOMP_INLINE, 0x52DE }, { 0xF930, 0, 1 | DECOMP_INLINE, 0x64C4 }, { 0xF931, 0, 1 | DECOMP_INLINE, 0x6AD3 }, { 0xF932, 0, 1 | DECOMP_INLINE, 0x7210 }, { 0xF933, 0, 1 | DECOMP_INLINE, 0x76E7 }, { 0xF934, 0, 1 | DECOMP_INLINE, 0x8001 }, { 0xF935, 0, 1 | DECOMP_INLINE, 0x8606 }, { 0xF936, 0, 1 | DECOMP_INLINE, 0x865C }, { 0xF937, 0, 1 | DECOMP_INLINE, 0x8DEF }, { 0xF938, 0, 1 | DECOMP_INLINE, 0x9732 }, { 0xF939, 0, 1 | DECOMP_INLINE, 0x9B6F }, { 0xF93A, 0, 1 | DECOMP_INLINE, 0x9DFA }, { 0xF93B, 0, 1 | DECOMP_INLINE, 0x788C }, { 0xF93C, 0, 1 | DECOMP_INLINE, 0x797F }, { 0xF93D, 0, 1 | DECOMP_INLINE, 0x7DA0 }, { 0xF93E, 0, 1 | DECOMP_INLINE, 0x83C9 }, { 0xF93F, 0, 1 | DECOMP_INLINE, 0x9304 }, { 0xF940, 0, 1 | DECOMP_INLINE, 0x9E7F }, { 0xF941, 0, 1 | DECOMP_INLINE, 0x8AD6 }, { 0xF942, 0, 1 | DECOMP_INLINE, 0x58DF }, { 0xF943, 0, 1 | DECOMP_INLINE, 0x5F04 }, { 0xF944, 0, 1 | DECOMP_INLINE, 0x7C60 }, { 0xF945, 0, 1 | DECOMP_INLINE, 0x807E }, { 0xF946, 0, 1 | DECOMP_INLINE, 0x7262 }, { 0xF947, 0, 1 | DECOMP_INLINE, 0x78CA }, { 0xF948, 0, 1 | DECOMP_INLINE, 0x8CC2 }, { 0xF949, 0, 1 | DECOMP_INLINE, 0x96F7 }, { 0xF94A, 0, 1 | DECOMP_INLINE, 0x58D8 }, { 0xF94B, 0, 1 | DECOMP_INLINE, 0x5C62 }, { 0xF94C, 0, 1 | DECOMP_INLINE, 0x6A13 }, { 0xF94D, 0, 1 | DECOMP_INLINE, 0x6DDA }, { 0xF94E, 0, 1 | DECOMP_INLINE, 0x6F0F }, { 0xF94F, 0, 1 | DECOMP_INLINE, 0x7D2F }, { 0xF950, 0, 1 | DECOMP_INLINE, 0x7E37 }, { 0xF951, 0, 1 | DECOMP_INLINE, 0x964B }, { 0xF952, 0, 1 | DECOMP_INLINE, 0x52D2 }, { 0xF953, 0, 1 | DECOMP_INLINE, 0x808B }, { 0xF954, 0, 1 | DECOMP_INLINE, 0x51DC }, { 0xF955, 0, 1 | DECOMP_INLINE, 0x51CC }, { 0xF956, 0, 1 | DECOMP_INLINE, 0x7A1C }, { 0xF957, 0, 1 | DECOMP_INLINE, 0x7DBE }, { 0xF958, 0, 1 | DECOMP_INLINE, 0x83F1 }, { 0xF959, 0, 1 | DECOMP_INLINE, 0x9675 }, { 0xF95A, 0, 1 | DECOMP_INLINE, 0x8B80 }, { 0xF95B, 0, 1 | DECOMP_INLINE, 0x62CF }, { 0xF95C, 0, 1 | DECOMP_INLINE, 0x6A02 }, { 0xF95D, 0, 1 | DECOMP_INLINE, 0x8AFE }, { 0xF95E, 0, 1 | DECOMP_INLINE, 0x4E39 }, { 0xF95F, 0, 1 | DECOMP_INLINE, 0x5BE7 }, { 0xF960, 0, 1 | DECOMP_INLINE, 0x6012 }, { 0xF961, 0, 1 | DECOMP_INLINE, 0x7387 }, { 0xF962, 0, 1 | DECOMP_INLINE, 0x7570 }, { 0xF963, 0, 1 | DECOMP_INLINE, 0x5317 }, { 0xF964, 0, 1 | DECOMP_INLINE, 0x78FB }, { 0xF965, 0, 1 | DECOMP_INLINE, 0x4FBF }, { 0xF966, 0, 1 | DECOMP_INLINE, 0x5FA9 }, { 0xF967, 0, 1 | DECOMP_INLINE, 0x4E0D }, { 0xF968, 0, 1 | DECOMP_INLINE, 0x6CCC }, { 0xF969, 0, 1 | DECOMP_INLINE, 0x6578 }, { 0xF96A, 0, 1 | DECOMP_INLINE, 0x7D22 }, { 0xF96B, 0, 1 | DECOMP_INLINE, 0x53C3 }, { 0xF96C, 0, 1 | DECOMP_INLINE, 0x585E }, { 0xF96D, 0, 1 | DECOMP_INLINE, 0x7701 }, { 0xF96E, 0, 1 | DECOMP_INLINE, 0x8449 }, { 0xF96F, 0, 1 | DECOMP_INLINE, 0x8AAA }, { 0xF970, 0, 1 | DECOMP_INLINE, 0x6BBA }, { 0xF971, 0, 1 | DECOMP_INLINE, 0x8FB0 }, { 0xF972, 0, 1 | DECOMP_INLINE, 0x6C88 }, { 0xF973, 0, 1 | DECOMP_INLINE, 0x62FE }, { 0xF974, 0, 1 | DECOMP_INLINE, 0x82E5 }, { 0xF975, 0, 1 | DECOMP_INLINE, 0x63A0 }, { 0xF976, 0, 1 | DECOMP_INLINE, 0x7565 }, { 0xF977, 0, 1 | DECOMP_INLINE, 0x4EAE }, { 0xF978, 0, 1 | DECOMP_INLINE, 0x5169 }, { 0xF979, 0, 1 | DECOMP_INLINE, 0x51C9 }, { 0xF97A, 0, 1 | DECOMP_INLINE, 0x6881 }, { 0xF97B, 0, 1 | DECOMP_INLINE, 0x7CE7 }, { 0xF97C, 0, 1 | DECOMP_INLINE, 0x826F }, { 0xF97D, 0, 1 | DECOMP_INLINE, 0x8AD2 }, { 0xF97E, 0, 1 | DECOMP_INLINE, 0x91CF }, { 0xF97F, 0, 1 | DECOMP_INLINE, 0x52F5 }, { 0xF980, 0, 1 | DECOMP_INLINE, 0x5442 }, { 0xF981, 0, 1 | DECOMP_INLINE, 0x5973 }, { 0xF982, 0, 1 | DECOMP_INLINE, 0x5EEC }, { 0xF983, 0, 1 | DECOMP_INLINE, 0x65C5 }, { 0xF984, 0, 1 | DECOMP_INLINE, 0x6FFE }, { 0xF985, 0, 1 | DECOMP_INLINE, 0x792A }, { 0xF986, 0, 1 | DECOMP_INLINE, 0x95AD }, { 0xF987, 0, 1 | DECOMP_INLINE, 0x9A6A }, { 0xF988, 0, 1 | DECOMP_INLINE, 0x9E97 }, { 0xF989, 0, 1 | DECOMP_INLINE, 0x9ECE }, { 0xF98A, 0, 1 | DECOMP_INLINE, 0x529B }, { 0xF98B, 0, 1 | DECOMP_INLINE, 0x66C6 }, { 0xF98C, 0, 1 | DECOMP_INLINE, 0x6B77 }, { 0xF98D, 0, 1 | DECOMP_INLINE, 0x8F62 }, { 0xF98E, 0, 1 | DECOMP_INLINE, 0x5E74 }, { 0xF98F, 0, 1 | DECOMP_INLINE, 0x6190 }, { 0xF990, 0, 1 | DECOMP_INLINE, 0x6200 }, { 0xF991, 0, 1 | DECOMP_INLINE, 0x649A }, { 0xF992, 0, 1 | DECOMP_INLINE, 0x6F23 }, { 0xF993, 0, 1 | DECOMP_INLINE, 0x7149 }, { 0xF994, 0, 1 | DECOMP_INLINE, 0x7489 }, { 0xF995, 0, 1 | DECOMP_INLINE, 0x79CA }, { 0xF996, 0, 1 | DECOMP_INLINE, 0x7DF4 }, { 0xF997, 0, 1 | DECOMP_INLINE, 0x806F }, { 0xF998, 0, 1 | DECOMP_INLINE, 0x8F26 }, { 0xF999, 0, 1 | DECOMP_INLINE, 0x84EE }, { 0xF99A, 0, 1 | DECOMP_INLINE, 0x9023 }, { 0xF99B, 0, 1 | DECOMP_INLINE, 0x934A }, { 0xF99C, 0, 1 | DECOMP_INLINE, 0x5217 }, { 0xF99D, 0, 1 | DECOMP_INLINE, 0x52A3 }, { 0xF99E, 0, 1 | DECOMP_INLINE, 0x54BD }, { 0xF99F, 0, 1 | DECOMP_INLINE, 0x70C8 }, { 0xF9A0, 0, 1 | DECOMP_INLINE, 0x88C2 }, { 0xF9A1, 0, 1 | DECOMP_INLINE, 0x8AAA }, { 0xF9A2, 0, 1 | DECOMP_INLINE, 0x5EC9 }, { 0xF9A3, 0, 1 | DECOMP_INLINE, 0x5FF5 }, { 0xF9A4, 0, 1 | DECOMP_INLINE, 0x637B }, { 0xF9A5, 0, 1 | DECOMP_INLINE, 0x6BAE }, { 0xF9A6, 0, 1 | DECOMP_INLINE, 0x7C3E }, { 0xF9A7, 0, 1 | DECOMP_INLINE, 0x7375 }, { 0xF9A8, 0, 1 | DECOMP_INLINE, 0x4EE4 }, { 0xF9A9, 0, 1 | DECOMP_INLINE, 0x56F9 }, { 0xF9AA, 0, 1 | DECOMP_INLINE, 0x5BE7 }, { 0xF9AB, 0, 1 | DECOMP_INLINE, 0x5DBA }, { 0xF9AC, 0, 1 | DECOMP_INLINE, 0x601C }, { 0xF9AD, 0, 1 | DECOMP_INLINE, 0x73B2 }, { 0xF9AE, 0, 1 | DECOMP_INLINE, 0x7469 }, { 0xF9AF, 0, 1 | DECOMP_INLINE, 0x7F9A }, { 0xF9B0, 0, 1 | DECOMP_INLINE, 0x8046 }, { 0xF9B1, 0, 1 | DECOMP_INLINE, 0x9234 }, { 0xF9B2, 0, 1 | DECOMP_INLINE, 0x96F6 }, { 0xF9B3, 0, 1 | DECOMP_INLINE, 0x9748 }, { 0xF9B4, 0, 1 | DECOMP_INLINE, 0x9818 }, { 0xF9B5, 0, 1 | DECOMP_INLINE, 0x4F8B }, { 0xF9B6, 0, 1 | DECOMP_INLINE, 0x79AE }, { 0xF9B7, 0, 1 | DECOMP_INLINE, 0x91B4 }, { 0xF9B8, 0, 1 | DECOMP_INLINE, 0x96B8 }, { 0xF9B9, 0, 1 | DECOMP_INLINE, 0x60E1 }, { 0xF9BA, 0, 1 | DECOMP_INLINE, 0x4E86 }, { 0xF9BB, 0, 1 | DECOMP_INLINE, 0x50DA }, { 0xF9BC, 0, 1 | DECOMP_INLINE, 0x5BEE }, { 0xF9BD, 0, 1 | DECOMP_INLINE, 0x5C3F }, { 0xF9BE, 0, 1 | DECOMP_INLINE, 0x6599 }, { 0xF9BF, 0, 1 | DECOMP_INLINE, 0x6A02 }, { 0xF9C0, 0, 1 | DECOMP_INLINE, 0x71CE }, { 0xF9C1, 0, 1 | DECOMP_INLINE, 0x7642 }, { 0xF9C2, 0, 1 | DECOMP_INLINE, 0x84FC }, { 0xF9C3, 0, 1 | DECOMP_INLINE, 0x907C }, { 0xF9C4, 0, 1 | DECOMP_INLINE, 0x9F8D }, { 0xF9C5, 0, 1 | DECOMP_INLINE, 0x6688 }, { 0xF9C6, 0, 1 | DECOMP_INLINE, 0x962E }, { 0xF9C7, 0, 1 | DECOMP_INLINE, 0x5289 }, { 0xF9C8, 0, 1 | DECOMP_INLINE, 0x677B }, { 0xF9C9, 0, 1 | DECOMP_INLINE, 0x67F3 }, { 0xF9CA, 0, 1 | DECOMP_INLINE, 0x6D41 }, { 0xF9CB, 0, 1 | DECOMP_INLINE, 0x6E9C }, { 0xF9CC, 0, 1 | DECOMP_INLINE, 0x7409 }, { 0xF9CD, 0, 1 | DECOMP_INLINE, 0x7559 }, { 0xF9CE, 0, 1 | DECOMP_INLINE, 0x786B }, { 0xF9CF, 0, 1 | DECOMP_INLINE, 0x7D10 }, { 0xF9D0, 0, 1 | DECOMP_INLINE, 0x985E }, { 0xF9D1, 0, 1 | DECOMP_INLINE, 0x516D }, { 0xF9D2, 0, 1 | DECOMP_INLINE, 0x622E }, { 0xF9D3, 0, 1 | DECOMP_INLINE, 0x9678 }, { 0xF9D4, 0, 1 | DECOMP_INLINE, 0x502B }, { 0xF9D5, 0, 1 | DECOMP_INLINE, 0x5D19 }, { 0xF9D6, 0, 1 | DECOMP_INLINE, 0x6DEA }, { 0xF9D7, 0, 1 | DECOMP_INLINE, 0x8F2A }, { 0xF9D8, 0, 1 | DECOMP_INLINE, 0x5F8B }, { 0xF9D9, 0, 1 | DECOMP_INLINE, 0x6144 }, { 0xF9DA, 0, 1 | DECOMP_INLINE, 0x6817 }, { 0xF9DB, 0, 1 | DECOMP_INLINE, 0x7387 }, { 0xF9DC, 0, 1 | DECOMP_INLINE, 0x9686 }, { 0xF9DD, 0, 1 | DECOMP_INLINE, 0x5229 }, { 0xF9DE, 0, 1 | DECOMP_INLINE, 0x540F }, { 0xF9DF, 0, 1 | DECOMP_INLINE, 0x5C65 }, { 0xF9E0, 0, 1 | DECOMP_INLINE, 0x6613 }, { 0xF9E1, 0, 1 | DECOMP_INLINE, 0x674E }, { 0xF9E2, 0, 1 | DECOMP_INLINE, 0x68A8 }, { 0xF9E3, 0, 1 | DECOMP_INLINE, 0x6CE5 }, { 0xF9E4, 0, 1 | DECOMP_INLINE, 0x7406 }, { 0xF9E5, 0, 1 | DECOMP_INLINE, 0x75E2 }, { 0xF9E6, 0, 1 | DECOMP_INLINE, 0x7F79 }, { 0xF9E7, 0, 1 | DECOMP_INLINE, 0x88CF }, { 0xF9E8, 0, 1 | DECOMP_INLINE, 0x88E1 }, { 0xF9E9, 0, 1 | DECOMP_INLINE, 0x91CC }, { 0xF9EA, 0, 1 | DECOMP_INLINE, 0x96E2 }, { 0xF9EB, 0, 1 | DECOMP_INLINE, 0x533F }, { 0xF9EC, 0, 1 | DECOMP_INLINE, 0x6EBA }, { 0xF9ED, 0, 1 | DECOMP_INLINE, 0x541D }, { 0xF9EE, 0, 1 | DECOMP_INLINE, 0x71D0 }, { 0xF9EF, 0, 1 | DECOMP_INLINE, 0x7498 }, { 0xF9F0, 0, 1 | DECOMP_INLINE, 0x85FA }, { 0xF9F1, 0, 1 | DECOMP_INLINE, 0x96A3 }, { 0xF9F2, 0, 1 | DECOMP_INLINE, 0x9C57 }, { 0xF9F3, 0, 1 | DECOMP_INLINE, 0x9E9F }, { 0xF9F4, 0, 1 | DECOMP_INLINE, 0x6797 }, { 0xF9F5, 0, 1 | DECOMP_INLINE, 0x6DCB }, { 0xF9F6, 0, 1 | DECOMP_INLINE, 0x81E8 }, { 0xF9F7, 0, 1 | DECOMP_INLINE, 0x7ACB }, { 0xF9F8, 0, 1 | DECOMP_INLINE, 0x7B20 }, { 0xF9F9, 0, 1 | DECOMP_INLINE, 0x7C92 }, { 0xF9FA, 0, 1 | DECOMP_INLINE, 0x72C0 }, { 0xF9FB, 0, 1 | DECOMP_INLINE, 0x7099 }, { 0xF9FC, 0, 1 | DECOMP_INLINE, 0x8B58 }, { 0xF9FD, 0, 1 | DECOMP_INLINE, 0x4EC0 }, { 0xF9FE, 0, 1 | DECOMP_INLINE, 0x8336 }, { 0xF9FF, 0, 1 | DECOMP_INLINE, 0x523A }, { 0xFA00, 0, 1 | DECOMP_INLINE, 0x5207 }, { 0xFA01, 0, 1 | DECOMP_INLINE, 0x5EA6 }, { 0xFA02, 0, 1 | DECOMP_INLINE, 0x62D3 }, { 0xFA03, 0, 1 | DECOMP_INLINE, 0x7CD6 }, { 0xFA04, 0, 1 | DECOMP_INLINE, 0x5B85 }, { 0xFA05, 0, 1 | DECOMP_INLINE, 0x6D1E }, { 0xFA06, 0, 1 | DECOMP_INLINE, 0x66B4 }, { 0xFA07, 0, 1 | DECOMP_INLINE, 0x8F3B }, { 0xFA08, 0, 1 | DECOMP_INLINE, 0x884C }, { 0xFA09, 0, 1 | DECOMP_INLINE, 0x964D }, { 0xFA0A, 0, 1 | DECOMP_INLINE, 0x898B }, { 0xFA0B, 0, 1 | DECOMP_INLINE, 0x5ED3 }, { 0xFA0C, 0, 1 | DECOMP_INLINE, 0x5140 }, { 0xFA0D, 0, 1 | DECOMP_INLINE, 0x55C0 }, { 0xFA10, 0, 1 | DECOMP_INLINE, 0x585A }, { 0xFA12, 0, 1 | DECOMP_INLINE, 0x6674 }, { 0xFA15, 0, 1 | DECOMP_INLINE, 0x51DE }, { 0xFA16, 0, 1 | DECOMP_INLINE, 0x732A }, { 0xFA17, 0, 1 | DECOMP_INLINE, 0x76CA }, { 0xFA18, 0, 1 | DECOMP_INLINE, 0x793C }, { 0xFA19, 0, 1 | DECOMP_INLINE, 0x795E }, { 0xFA1A, 0, 1 | DECOMP_INLINE, 0x7965 }, { 0xFA1B, 0, 1 | DECOMP_INLINE, 0x798F }, { 0xFA1C, 0, 1 | DECOMP_INLINE, 0x9756 }, { 0xFA1D, 0, 1 | DECOMP_INLINE, 0x7CBE }, { 0xFA1E, 0, 1 | DECOMP_INLINE, 0x7FBD }, { 0xFA20, 0, 1 | DECOMP_INLINE, 0x8612 }, { 0xFA22, 0, 1 | DECOMP_INLINE, 0x8AF8 }, { 0xFA25, 0, 1 | DECOMP_INLINE, 0x9038 }, { 0xFA26, 0, 1 | DECOMP_INLINE, 0x90FD }, { 0xFA2A, 0, 1 | DECOMP_INLINE, 0x98EF }, { 0xFA2B, 0, 1 | DECOMP_INLINE, 0x98FC }, { 0xFA2C, 0, 1 | DECOMP_INLINE, 0x9928 }, { 0xFA2D, 0, 1 | DECOMP_INLINE, 0x9DB4 }, { 0xFA2E, 0, 1 | DECOMP_INLINE, 0x90DE }, { 0xFA2F, 0, 1 | DECOMP_INLINE, 0x96B7 }, { 0xFA30, 0, 1 | DECOMP_INLINE, 0x4FAE }, { 0xFA31, 0, 1 | DECOMP_INLINE, 0x50E7 }, { 0xFA32, 0, 1 | DECOMP_INLINE, 0x514D }, { 0xFA33, 0, 1 | DECOMP_INLINE, 0x52C9 }, { 0xFA34, 0, 1 | DECOMP_INLINE, 0x52E4 }, { 0xFA35, 0, 1 | DECOMP_INLINE, 0x5351 }, { 0xFA36, 0, 1 | DECOMP_INLINE, 0x559D }, { 0xFA37, 0, 1 | DECOMP_INLINE, 0x5606 }, { 0xFA38, 0, 1 | DECOMP_INLINE, 0x5668 }, { 0xFA39, 0, 1 | DECOMP_INLINE, 0x5840 }, { 0xFA3A, 0, 1 | DECOMP_INLINE, 0x58A8 }, { 0xFA3B, 0, 1 | DECOMP_INLINE, 0x5C64 }, { 0xFA3C, 0, 1 | DECOMP_INLINE, 0x5C6E }, { 0xFA3D, 0, 1 | DECOMP_INLINE, 0x6094 }, { 0xFA3E, 0, 1 | DECOMP_INLINE, 0x6168 }, { 0xFA3F, 0, 1 | DECOMP_INLINE, 0x618E }, { 0xFA40, 0, 1 | DECOMP_INLINE, 0x61F2 }, { 0xFA41, 0, 1 | DECOMP_INLINE, 0x654F }, { 0xFA42, 0, 1 | DECOMP_INLINE, 0x65E2 }, { 0xFA43, 0, 1 | DECOMP_INLINE, 0x6691 }, { 0xFA44, 0, 1 | DECOMP_INLINE, 0x6885 }, { 0xFA45, 0, 1 | DECOMP_INLINE, 0x6D77 }, { 0xFA46, 0, 1 | DECOMP_INLINE, 0x6E1A }, { 0xFA47, 0, 1 | DECOMP_INLINE, 0x6F22 }, { 0xFA48, 0, 1 | DECOMP_INLINE, 0x716E }, { 0xFA49, 0, 1 | DECOMP_INLINE, 0x722B }, { 0xFA4A, 0, 1 | DECOMP_INLINE, 0x7422 }, { 0xFA4B, 0, 1 | DECOMP_INLINE, 0x7891 }, { 0xFA4C, 0, 1 | DECOMP_INLINE, 0x793E }, { 0xFA4D, 0, 1 | DECOMP_INLINE, 0x7949 }, { 0xFA4E, 0, 1 | DECOMP_INLINE, 0x7948 }, { 0xFA4F, 0, 1 | DECOMP_INLINE, 0x7950 }, { 0xFA50, 0, 1 | DECOMP_INLINE, 0x7956 }, { 0xFA51, 0, 1 | DECOMP_INLINE, 0x795D }, { 0xFA52, 0, 1 | DECOMP_INLINE, 0x798D }, { 0xFA53, 0, 1 | DECOMP_INLINE, 0x798E }, { 0xFA54, 0, 1 | DECOMP_INLINE, 0x7A40 }, { 0xFA55, 0, 1 | DECOMP_INLINE, 0x7A81 }, { 0xFA56, 0, 1 | DECOMP_INLINE, 0x7BC0 }, { 0xFA57, 0, 1 | DECOMP_INLINE, 0x7DF4 }, { 0xFA58, 0, 1 | DECOMP_INLINE, 0x7E09 }, { 0xFA59, 0, 1 | DECOMP_INLINE, 0x7E41 }, { 0xFA5A, 0, 1 | DECOMP_INLINE, 0x7F72 }, { 0xFA5B, 0, 1 | DECOMP_INLINE, 0x8005 }, { 0xFA5C, 0, 1 | DECOMP_INLINE, 0x81ED }, { 0xFA5D, 0, 1 | DECOMP_INLINE, 0x8279 }, { 0xFA5E, 0, 1 | DECOMP_INLINE, 0x8279 }, { 0xFA5F, 0, 1 | DECOMP_INLINE, 0x8457 }, { 0xFA60, 0, 1 | DECOMP_INLINE, 0x8910 }, { 0xFA61, 0, 1 | DECOMP_INLINE, 0x8996 }, { 0xFA62, 0, 1 | DECOMP_INLINE, 0x8B01 }, { 0xFA63, 0, 1 | DECOMP_INLINE, 0x8B39 }, { 0xFA64, 0, 1 | DECOMP_INLINE, 0x8CD3 }, { 0xFA65, 0, 1 | DECOMP_INLINE, 0x8D08 }, { 0xFA66, 0, 1 | DECOMP_INLINE, 0x8FB6 }, { 0xFA67, 0, 1 | DECOMP_INLINE, 0x9038 }, { 0xFA68, 0, 1 | DECOMP_INLINE, 0x96E3 }, { 0xFA69, 0, 1 | DECOMP_INLINE, 0x97FF }, { 0xFA6A, 0, 1 | DECOMP_INLINE, 0x983B }, { 0xFA6B, 0, 1 | DECOMP_INLINE, 0x6075 }, { 0xFA6C, 0, 1, 3527 }, { 0xFA6D, 0, 1 | DECOMP_INLINE, 0x8218 }, { 0xFA70, 0, 1 | DECOMP_INLINE, 0x4E26 }, { 0xFA71, 0, 1 | DECOMP_INLINE, 0x51B5 }, { 0xFA72, 0, 1 | DECOMP_INLINE, 0x5168 }, { 0xFA73, 0, 1 | DECOMP_INLINE, 0x4F80 }, { 0xFA74, 0, 1 | DECOMP_INLINE, 0x5145 }, { 0xFA75, 0, 1 | DECOMP_INLINE, 0x5180 }, { 0xFA76, 0, 1 | DECOMP_INLINE, 0x52C7 }, { 0xFA77, 0, 1 | DECOMP_INLINE, 0x52FA }, { 0xFA78, 0, 1 | DECOMP_INLINE, 0x559D }, { 0xFA79, 0, 1 | DECOMP_INLINE, 0x5555 }, { 0xFA7A, 0, 1 | DECOMP_INLINE, 0x5599 }, { 0xFA7B, 0, 1 | DECOMP_INLINE, 0x55E2 }, { 0xFA7C, 0, 1 | DECOMP_INLINE, 0x585A }, { 0xFA7D, 0, 1 | DECOMP_INLINE, 0x58B3 }, { 0xFA7E, 0, 1 | DECOMP_INLINE, 0x5944 }, { 0xFA7F, 0, 1 | DECOMP_INLINE, 0x5954 }, { 0xFA80, 0, 1 | DECOMP_INLINE, 0x5A62 }, { 0xFA81, 0, 1 | DECOMP_INLINE, 0x5B28 }, { 0xFA82, 0, 1 | DECOMP_INLINE, 0x5ED2 }, { 0xFA83, 0, 1 | DECOMP_INLINE, 0x5ED9 }, { 0xFA84, 0, 1 | DECOMP_INLINE, 0x5F69 }, { 0xFA85, 0, 1 | DECOMP_INLINE, 0x5FAD }, { 0xFA86, 0, 1 | DECOMP_INLINE, 0x60D8 }, { 0xFA87, 0, 1 | DECOMP_INLINE, 0x614E }, { 0xFA88, 0, 1 | DECOMP_INLINE, 0x6108 }, { 0xFA89, 0, 1 | DECOMP_INLINE, 0x618E }, { 0xFA8A, 0, 1 | DECOMP_INLINE, 0x6160 }, { 0xFA8B, 0, 1 | DECOMP_INLINE, 0x61F2 }, { 0xFA8C, 0, 1 | DECOMP_INLINE, 0x6234 }, { 0xFA8D, 0, 1 | DECOMP_INLINE, 0x63C4 }, { 0xFA8E, 0, 1 | DECOMP_INLINE, 0x641C }, { 0xFA8F, 0, 1 | DECOMP_INLINE, 0x6452 }, { 0xFA90, 0, 1 | DECOMP_INLINE, 0x6556 }, { 0xFA91, 0, 1 | DECOMP_INLINE, 0x6674 }, { 0xFA92, 0, 1 | DECOMP_INLINE, 0x6717 }, { 0xFA93, 0, 1 | DECOMP_INLINE, 0x671B }, { 0xFA94, 0, 1 | DECOMP_INLINE, 0x6756 }, { 0xFA95, 0, 1 | DECOMP_INLINE, 0x6B79 }, { 0xFA96, 0, 1 | DECOMP_INLINE, 0x6BBA }, { 0xFA97, 0, 1 | DECOMP_INLINE, 0x6D41 }, { 0xFA98, 0, 1 | DECOMP_INLINE, 0x6EDB }, { 0xFA99, 0, 1 | DECOMP_INLINE, 0x6ECB }, { 0xFA9A, 0, 1 | DECOMP_INLINE, 0x6F22 }, { 0xFA9B, 0, 1 | DECOMP_INLINE, 0x701E }, { 0xFA9C, 0, 1 | DECOMP_INLINE, 0x716E }, { 0xFA9D, 0, 1 | DECOMP_INLINE, 0x77A7 }, { 0xFA9E, 0, 1 | DECOMP_INLINE, 0x7235 }, { 0xFA9F, 0, 1 | DECOMP_INLINE, 0x72AF }, { 0xFAA0, 0, 1 | DECOMP_INLINE, 0x732A }, { 0xFAA1, 0, 1 | DECOMP_INLINE, 0x7471 }, { 0xFAA2, 0, 1 | DECOMP_INLINE, 0x7506 }, { 0xFAA3, 0, 1 | DECOMP_INLINE, 0x753B }, { 0xFAA4, 0, 1 | DECOMP_INLINE, 0x761D }, { 0xFAA5, 0, 1 | DECOMP_INLINE, 0x761F }, { 0xFAA6, 0, 1 | DECOMP_INLINE, 0x76CA }, { 0xFAA7, 0, 1 | DECOMP_INLINE, 0x76DB }, { 0xFAA8, 0, 1 | DECOMP_INLINE, 0x76F4 }, { 0xFAA9, 0, 1 | DECOMP_INLINE, 0x774A }, { 0xFAAA, 0, 1 | DECOMP_INLINE, 0x7740 }, { 0xFAAB, 0, 1 | DECOMP_INLINE, 0x78CC }, { 0xFAAC, 0, 1 | DECOMP_INLINE, 0x7AB1 }, { 0xFAAD, 0, 1 | DECOMP_INLINE, 0x7BC0 }, { 0xFAAE, 0, 1 | DECOMP_INLINE, 0x7C7B }, { 0xFAAF, 0, 1 | DECOMP_INLINE, 0x7D5B }, { 0xFAB0, 0, 1 | DECOMP_INLINE, 0x7DF4 }, { 0xFAB1, 0, 1 | DECOMP_INLINE, 0x7F3E }, { 0xFAB2, 0, 1 | DECOMP_INLINE, 0x8005 }, { 0xFAB3, 0, 1 | DECOMP_INLINE, 0x8352 }, { 0xFAB4, 0, 1 | DECOMP_INLINE, 0x83EF }, { 0xFAB5, 0, 1 | DECOMP_INLINE, 0x8779 }, { 0xFAB6, 0, 1 | DECOMP_INLINE, 0x8941 }, { 0xFAB7, 0, 1 | DECOMP_INLINE, 0x8986 }, { 0xFAB8, 0, 1 | DECOMP_INLINE, 0x8996 }, { 0xFAB9, 0, 1 | DECOMP_INLINE, 0x8ABF }, { 0xFABA, 0, 1 | DECOMP_INLINE, 0x8AF8 }, { 0xFABB, 0, 1 | DECOMP_INLINE, 0x8ACB }, { 0xFABC, 0, 1 | DECOMP_INLINE, 0x8B01 }, { 0xFABD, 0, 1 | DECOMP_INLINE, 0x8AFE }, { 0xFABE, 0, 1 | DECOMP_INLINE, 0x8AED }, { 0xFABF, 0, 1 | DECOMP_INLINE, 0x8B39 }, { 0xFAC0, 0, 1 | DECOMP_INLINE, 0x8B8A }, { 0xFAC1, 0, 1 | DECOMP_INLINE, 0x8D08 }, { 0xFAC2, 0, 1 | DECOMP_INLINE, 0x8F38 }, { 0xFAC3, 0, 1 | DECOMP_INLINE, 0x9072 }, { 0xFAC4, 0, 1 | DECOMP_INLINE, 0x9199 }, { 0xFAC5, 0, 1 | DECOMP_INLINE, 0x9276 }, { 0xFAC6, 0, 1 | DECOMP_INLINE, 0x967C }, { 0xFAC7, 0, 1 | DECOMP_INLINE, 0x96E3 }, { 0xFAC8, 0, 1 | DECOMP_INLINE, 0x9756 }, { 0xFAC9, 0, 1 | DECOMP_INLINE, 0x97DB }, { 0xFACA, 0, 1 | DECOMP_INLINE, 0x97FF }, { 0xFACB, 0, 1 | DECOMP_INLINE, 0x980B }, { 0xFACC, 0, 1 | DECOMP_INLINE, 0x983B }, { 0xFACD, 0, 1 | DECOMP_INLINE, 0x9B12 }, { 0xFACE, 0, 1 | DECOMP_INLINE, 0x9F9C }, { 0xFACF, 0, 1, 3528 }, { 0xFAD0, 0, 1, 3529 }, { 0xFAD1, 0, 1, 3530 }, { 0xFAD2, 0, 1 | DECOMP_INLINE, 0x3B9D }, { 0xFAD3, 0, 1 | DECOMP_INLINE, 0x4018 }, { 0xFAD4, 0, 1 | DECOMP_INLINE, 0x4039 }, { 0xFAD5, 0, 1, 3531 }, { 0xFAD6, 0, 1, 3532 }, { 0xFAD7, 0, 1, 3533 }, { 0xFAD8, 0, 1 | DECOMP_INLINE, 0x9F43 }, { 0xFAD9, 0, 1 | DECOMP_INLINE, 0x9F8E }, { 0xFB00, 0, 2 | DECOMP_COMPAT, 3534 }, { 0xFB01, 0, 2 | DECOMP_COMPAT, 3536 }, { 0xFB02, 0, 2 | DECOMP_COMPAT, 3538 }, { 0xFB03, 0, 3 | DECOMP_COMPAT, 3540 }, { 0xFB04, 0, 3 | DECOMP_COMPAT, 3543 }, { 0xFB05, 0, 2 | DECOMP_COMPAT, 3546 }, { 0xFB06, 0, 2 | DECOMP_COMPAT, 3548 }, { 0xFB13, 0, 2 | DECOMP_COMPAT, 3550 }, { 0xFB14, 0, 2 | DECOMP_COMPAT, 3552 }, { 0xFB15, 0, 2 | DECOMP_COMPAT, 3554 }, { 0xFB16, 0, 2 | DECOMP_COMPAT, 3556 }, { 0xFB17, 0, 2 | DECOMP_COMPAT, 3558 }, { 0xFB1D, 0, 2 | DECOMP_NO_COMPOSE, 3560 }, /* in exclusion list */ { 0xFB1E, 26, 0, 0 }, { 0xFB1F, 0, 2 | DECOMP_NO_COMPOSE, 3562 }, /* in exclusion list */ { 0xFB20, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05E2 }, { 0xFB21, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D0 }, { 0xFB22, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D3 }, { 0xFB23, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05D4 }, { 0xFB24, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05DB }, { 0xFB25, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05DC }, { 0xFB26, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05DD }, { 0xFB27, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05E8 }, { 0xFB28, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x05EA }, { 0xFB29, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002B }, { 0xFB2A, 0, 2 | DECOMP_NO_COMPOSE, 3564 }, /* in exclusion list */ { 0xFB2B, 0, 2 | DECOMP_NO_COMPOSE, 3566 }, /* in exclusion list */ { 0xFB2C, 0, 2 | DECOMP_NO_COMPOSE, 3568 }, /* in exclusion list */ { 0xFB2D, 0, 2 | DECOMP_NO_COMPOSE, 3570 }, /* in exclusion list */ { 0xFB2E, 0, 2 | DECOMP_NO_COMPOSE, 3572 }, /* in exclusion list */ { 0xFB2F, 0, 2 | DECOMP_NO_COMPOSE, 3574 }, /* in exclusion list */ { 0xFB30, 0, 2 | DECOMP_NO_COMPOSE, 3576 }, /* in exclusion list */ { 0xFB31, 0, 2 | DECOMP_NO_COMPOSE, 3578 }, /* in exclusion list */ { 0xFB32, 0, 2 | DECOMP_NO_COMPOSE, 3580 }, /* in exclusion list */ { 0xFB33, 0, 2 | DECOMP_NO_COMPOSE, 3582 }, /* in exclusion list */ { 0xFB34, 0, 2 | DECOMP_NO_COMPOSE, 3584 }, /* in exclusion list */ { 0xFB35, 0, 2 | DECOMP_NO_COMPOSE, 3586 }, /* in exclusion list */ { 0xFB36, 0, 2 | DECOMP_NO_COMPOSE, 3588 }, /* in exclusion list */ { 0xFB38, 0, 2 | DECOMP_NO_COMPOSE, 3590 }, /* in exclusion list */ { 0xFB39, 0, 2 | DECOMP_NO_COMPOSE, 3592 }, /* in exclusion list */ { 0xFB3A, 0, 2 | DECOMP_NO_COMPOSE, 3594 }, /* in exclusion list */ { 0xFB3B, 0, 2 | DECOMP_NO_COMPOSE, 3596 }, /* in exclusion list */ { 0xFB3C, 0, 2 | DECOMP_NO_COMPOSE, 3598 }, /* in exclusion list */ { 0xFB3E, 0, 2 | DECOMP_NO_COMPOSE, 3600 }, /* in exclusion list */ { 0xFB40, 0, 2 | DECOMP_NO_COMPOSE, 3602 }, /* in exclusion list */ { 0xFB41, 0, 2 | DECOMP_NO_COMPOSE, 3604 }, /* in exclusion list */ { 0xFB43, 0, 2 | DECOMP_NO_COMPOSE, 3606 }, /* in exclusion list */ { 0xFB44, 0, 2 | DECOMP_NO_COMPOSE, 3608 }, /* in exclusion list */ { 0xFB46, 0, 2 | DECOMP_NO_COMPOSE, 3610 }, /* in exclusion list */ { 0xFB47, 0, 2 | DECOMP_NO_COMPOSE, 3612 }, /* in exclusion list */ { 0xFB48, 0, 2 | DECOMP_NO_COMPOSE, 3614 }, /* in exclusion list */ { 0xFB49, 0, 2 | DECOMP_NO_COMPOSE, 3616 }, /* in exclusion list */ { 0xFB4A, 0, 2 | DECOMP_NO_COMPOSE, 3618 }, /* in exclusion list */ { 0xFB4B, 0, 2 | DECOMP_NO_COMPOSE, 3620 }, /* in exclusion list */ { 0xFB4C, 0, 2 | DECOMP_NO_COMPOSE, 3622 }, /* in exclusion list */ { 0xFB4D, 0, 2 | DECOMP_NO_COMPOSE, 3624 }, /* in exclusion list */ { 0xFB4E, 0, 2 | DECOMP_NO_COMPOSE, 3626 }, /* in exclusion list */ { 0xFB4F, 0, 2 | DECOMP_COMPAT, 3628 }, { 0xFB50, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0671 }, { 0xFB51, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0671 }, { 0xFB52, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067B }, { 0xFB53, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067B }, { 0xFB54, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067B }, { 0xFB55, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067B }, { 0xFB56, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067E }, { 0xFB57, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067E }, { 0xFB58, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067E }, { 0xFB59, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067E }, { 0xFB5A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0680 }, { 0xFB5B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0680 }, { 0xFB5C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0680 }, { 0xFB5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0680 }, { 0xFB5E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067A }, { 0xFB5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067A }, { 0xFB60, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067A }, { 0xFB61, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067A }, { 0xFB62, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067F }, { 0xFB63, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067F }, { 0xFB64, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067F }, { 0xFB65, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x067F }, { 0xFB66, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0679 }, { 0xFB67, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0679 }, { 0xFB68, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0679 }, { 0xFB69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0679 }, { 0xFB6A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A4 }, { 0xFB6B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A4 }, { 0xFB6C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A4 }, { 0xFB6D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A4 }, { 0xFB6E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A6 }, { 0xFB6F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A6 }, { 0xFB70, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A6 }, { 0xFB71, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A6 }, { 0xFB72, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0684 }, { 0xFB73, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0684 }, { 0xFB74, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0684 }, { 0xFB75, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0684 }, { 0xFB76, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0683 }, { 0xFB77, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0683 }, { 0xFB78, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0683 }, { 0xFB79, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0683 }, { 0xFB7A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0686 }, { 0xFB7B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0686 }, { 0xFB7C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0686 }, { 0xFB7D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0686 }, { 0xFB7E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0687 }, { 0xFB7F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0687 }, { 0xFB80, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0687 }, { 0xFB81, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0687 }, { 0xFB82, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x068D }, { 0xFB83, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x068D }, { 0xFB84, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x068C }, { 0xFB85, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x068C }, { 0xFB86, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x068E }, { 0xFB87, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x068E }, { 0xFB88, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0688 }, { 0xFB89, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0688 }, { 0xFB8A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0698 }, { 0xFB8B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0698 }, { 0xFB8C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0691 }, { 0xFB8D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0691 }, { 0xFB8E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A9 }, { 0xFB8F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A9 }, { 0xFB90, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A9 }, { 0xFB91, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A9 }, { 0xFB92, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AF }, { 0xFB93, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AF }, { 0xFB94, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AF }, { 0xFB95, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AF }, { 0xFB96, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B3 }, { 0xFB97, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B3 }, { 0xFB98, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B3 }, { 0xFB99, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B3 }, { 0xFB9A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B1 }, { 0xFB9B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B1 }, { 0xFB9C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B1 }, { 0xFB9D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06B1 }, { 0xFB9E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BA }, { 0xFB9F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BA }, { 0xFBA0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BB }, { 0xFBA1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BB }, { 0xFBA2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BB }, { 0xFBA3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BB }, { 0xFBA4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C0 }, { 0xFBA5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C0 }, { 0xFBA6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C1 }, { 0xFBA7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C1 }, { 0xFBA8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C1 }, { 0xFBA9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C1 }, { 0xFBAA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BE }, { 0xFBAB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BE }, { 0xFBAC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BE }, { 0xFBAD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BE }, { 0xFBAE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D2 }, { 0xFBAF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D2 }, { 0xFBB0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D3 }, { 0xFBB1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D3 }, { 0xFBD3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AD }, { 0xFBD4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AD }, { 0xFBD5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AD }, { 0xFBD6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06AD }, { 0xFBD7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C7 }, { 0xFBD8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C7 }, { 0xFBD9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C6 }, { 0xFBDA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C6 }, { 0xFBDB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C8 }, { 0xFBDC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C8 }, { 0xFBDD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0677 }, { 0xFBDE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06CB }, { 0xFBDF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06CB }, { 0xFBE0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C5 }, { 0xFBE1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C5 }, { 0xFBE2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C9 }, { 0xFBE3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06C9 }, { 0xFBE4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D0 }, { 0xFBE5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D0 }, { 0xFBE6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D0 }, { 0xFBE7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06D0 }, { 0xFBE8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0649 }, { 0xFBE9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0649 }, { 0xFBEA, 0, 2 | DECOMP_COMPAT, 3630 }, { 0xFBEB, 0, 2 | DECOMP_COMPAT, 3632 }, { 0xFBEC, 0, 2 | DECOMP_COMPAT, 3634 }, { 0xFBED, 0, 2 | DECOMP_COMPAT, 3636 }, { 0xFBEE, 0, 2 | DECOMP_COMPAT, 3638 }, { 0xFBEF, 0, 2 | DECOMP_COMPAT, 3640 }, { 0xFBF0, 0, 2 | DECOMP_COMPAT, 3642 }, { 0xFBF1, 0, 2 | DECOMP_COMPAT, 3644 }, { 0xFBF2, 0, 2 | DECOMP_COMPAT, 3646 }, { 0xFBF3, 0, 2 | DECOMP_COMPAT, 3648 }, { 0xFBF4, 0, 2 | DECOMP_COMPAT, 3650 }, { 0xFBF5, 0, 2 | DECOMP_COMPAT, 3652 }, { 0xFBF6, 0, 2 | DECOMP_COMPAT, 3654 }, { 0xFBF7, 0, 2 | DECOMP_COMPAT, 3656 }, { 0xFBF8, 0, 2 | DECOMP_COMPAT, 3658 }, { 0xFBF9, 0, 2 | DECOMP_COMPAT, 3660 }, { 0xFBFA, 0, 2 | DECOMP_COMPAT, 3662 }, { 0xFBFB, 0, 2 | DECOMP_COMPAT, 3664 }, { 0xFBFC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06CC }, { 0xFBFD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06CC }, { 0xFBFE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06CC }, { 0xFBFF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06CC }, { 0xFC00, 0, 2 | DECOMP_COMPAT, 3666 }, { 0xFC01, 0, 2 | DECOMP_COMPAT, 3668 }, { 0xFC02, 0, 2 | DECOMP_COMPAT, 3670 }, { 0xFC03, 0, 2 | DECOMP_COMPAT, 3672 }, { 0xFC04, 0, 2 | DECOMP_COMPAT, 3674 }, { 0xFC05, 0, 2 | DECOMP_COMPAT, 3676 }, { 0xFC06, 0, 2 | DECOMP_COMPAT, 3678 }, { 0xFC07, 0, 2 | DECOMP_COMPAT, 3680 }, { 0xFC08, 0, 2 | DECOMP_COMPAT, 3682 }, { 0xFC09, 0, 2 | DECOMP_COMPAT, 3684 }, { 0xFC0A, 0, 2 | DECOMP_COMPAT, 3686 }, { 0xFC0B, 0, 2 | DECOMP_COMPAT, 3688 }, { 0xFC0C, 0, 2 | DECOMP_COMPAT, 3690 }, { 0xFC0D, 0, 2 | DECOMP_COMPAT, 3692 }, { 0xFC0E, 0, 2 | DECOMP_COMPAT, 3694 }, { 0xFC0F, 0, 2 | DECOMP_COMPAT, 3696 }, { 0xFC10, 0, 2 | DECOMP_COMPAT, 3698 }, { 0xFC11, 0, 2 | DECOMP_COMPAT, 3700 }, { 0xFC12, 0, 2 | DECOMP_COMPAT, 3702 }, { 0xFC13, 0, 2 | DECOMP_COMPAT, 3704 }, { 0xFC14, 0, 2 | DECOMP_COMPAT, 3706 }, { 0xFC15, 0, 2 | DECOMP_COMPAT, 3708 }, { 0xFC16, 0, 2 | DECOMP_COMPAT, 3710 }, { 0xFC17, 0, 2 | DECOMP_COMPAT, 3712 }, { 0xFC18, 0, 2 | DECOMP_COMPAT, 3714 }, { 0xFC19, 0, 2 | DECOMP_COMPAT, 3716 }, { 0xFC1A, 0, 2 | DECOMP_COMPAT, 3718 }, { 0xFC1B, 0, 2 | DECOMP_COMPAT, 3720 }, { 0xFC1C, 0, 2 | DECOMP_COMPAT, 3722 }, { 0xFC1D, 0, 2 | DECOMP_COMPAT, 3724 }, { 0xFC1E, 0, 2 | DECOMP_COMPAT, 3726 }, { 0xFC1F, 0, 2 | DECOMP_COMPAT, 3728 }, { 0xFC20, 0, 2 | DECOMP_COMPAT, 3730 }, { 0xFC21, 0, 2 | DECOMP_COMPAT, 3732 }, { 0xFC22, 0, 2 | DECOMP_COMPAT, 3734 }, { 0xFC23, 0, 2 | DECOMP_COMPAT, 3736 }, { 0xFC24, 0, 2 | DECOMP_COMPAT, 3738 }, { 0xFC25, 0, 2 | DECOMP_COMPAT, 3740 }, { 0xFC26, 0, 2 | DECOMP_COMPAT, 3742 }, { 0xFC27, 0, 2 | DECOMP_COMPAT, 3744 }, { 0xFC28, 0, 2 | DECOMP_COMPAT, 3746 }, { 0xFC29, 0, 2 | DECOMP_COMPAT, 3748 }, { 0xFC2A, 0, 2 | DECOMP_COMPAT, 3750 }, { 0xFC2B, 0, 2 | DECOMP_COMPAT, 3752 }, { 0xFC2C, 0, 2 | DECOMP_COMPAT, 3754 }, { 0xFC2D, 0, 2 | DECOMP_COMPAT, 3756 }, { 0xFC2E, 0, 2 | DECOMP_COMPAT, 3758 }, { 0xFC2F, 0, 2 | DECOMP_COMPAT, 3760 }, { 0xFC30, 0, 2 | DECOMP_COMPAT, 3762 }, { 0xFC31, 0, 2 | DECOMP_COMPAT, 3764 }, { 0xFC32, 0, 2 | DECOMP_COMPAT, 3766 }, { 0xFC33, 0, 2 | DECOMP_COMPAT, 3768 }, { 0xFC34, 0, 2 | DECOMP_COMPAT, 3770 }, { 0xFC35, 0, 2 | DECOMP_COMPAT, 3772 }, { 0xFC36, 0, 2 | DECOMP_COMPAT, 3774 }, { 0xFC37, 0, 2 | DECOMP_COMPAT, 3776 }, { 0xFC38, 0, 2 | DECOMP_COMPAT, 3778 }, { 0xFC39, 0, 2 | DECOMP_COMPAT, 3780 }, { 0xFC3A, 0, 2 | DECOMP_COMPAT, 3782 }, { 0xFC3B, 0, 2 | DECOMP_COMPAT, 3784 }, { 0xFC3C, 0, 2 | DECOMP_COMPAT, 3786 }, { 0xFC3D, 0, 2 | DECOMP_COMPAT, 3788 }, { 0xFC3E, 0, 2 | DECOMP_COMPAT, 3790 }, { 0xFC3F, 0, 2 | DECOMP_COMPAT, 3792 }, { 0xFC40, 0, 2 | DECOMP_COMPAT, 3794 }, { 0xFC41, 0, 2 | DECOMP_COMPAT, 3796 }, { 0xFC42, 0, 2 | DECOMP_COMPAT, 3798 }, { 0xFC43, 0, 2 | DECOMP_COMPAT, 3800 }, { 0xFC44, 0, 2 | DECOMP_COMPAT, 3802 }, { 0xFC45, 0, 2 | DECOMP_COMPAT, 3804 }, { 0xFC46, 0, 2 | DECOMP_COMPAT, 3806 }, { 0xFC47, 0, 2 | DECOMP_COMPAT, 3808 }, { 0xFC48, 0, 2 | DECOMP_COMPAT, 3810 }, { 0xFC49, 0, 2 | DECOMP_COMPAT, 3812 }, { 0xFC4A, 0, 2 | DECOMP_COMPAT, 3814 }, { 0xFC4B, 0, 2 | DECOMP_COMPAT, 3816 }, { 0xFC4C, 0, 2 | DECOMP_COMPAT, 3818 }, { 0xFC4D, 0, 2 | DECOMP_COMPAT, 3820 }, { 0xFC4E, 0, 2 | DECOMP_COMPAT, 3822 }, { 0xFC4F, 0, 2 | DECOMP_COMPAT, 3824 }, { 0xFC50, 0, 2 | DECOMP_COMPAT, 3826 }, { 0xFC51, 0, 2 | DECOMP_COMPAT, 3828 }, { 0xFC52, 0, 2 | DECOMP_COMPAT, 3830 }, { 0xFC53, 0, 2 | DECOMP_COMPAT, 3832 }, { 0xFC54, 0, 2 | DECOMP_COMPAT, 3834 }, { 0xFC55, 0, 2 | DECOMP_COMPAT, 3836 }, { 0xFC56, 0, 2 | DECOMP_COMPAT, 3838 }, { 0xFC57, 0, 2 | DECOMP_COMPAT, 3840 }, { 0xFC58, 0, 2 | DECOMP_COMPAT, 3842 }, { 0xFC59, 0, 2 | DECOMP_COMPAT, 3844 }, { 0xFC5A, 0, 2 | DECOMP_COMPAT, 3846 }, { 0xFC5B, 0, 2 | DECOMP_COMPAT, 3848 }, { 0xFC5C, 0, 2 | DECOMP_COMPAT, 3850 }, { 0xFC5D, 0, 2 | DECOMP_COMPAT, 3852 }, { 0xFC5E, 0, 3 | DECOMP_COMPAT, 3854 }, { 0xFC5F, 0, 3 | DECOMP_COMPAT, 3857 }, { 0xFC60, 0, 3 | DECOMP_COMPAT, 3860 }, { 0xFC61, 0, 3 | DECOMP_COMPAT, 3863 }, { 0xFC62, 0, 3 | DECOMP_COMPAT, 3866 }, { 0xFC63, 0, 3 | DECOMP_COMPAT, 3869 }, { 0xFC64, 0, 2 | DECOMP_COMPAT, 3872 }, { 0xFC65, 0, 2 | DECOMP_COMPAT, 3874 }, { 0xFC66, 0, 2 | DECOMP_COMPAT, 3876 }, { 0xFC67, 0, 2 | DECOMP_COMPAT, 3878 }, { 0xFC68, 0, 2 | DECOMP_COMPAT, 3880 }, { 0xFC69, 0, 2 | DECOMP_COMPAT, 3882 }, { 0xFC6A, 0, 2 | DECOMP_COMPAT, 3884 }, { 0xFC6B, 0, 2 | DECOMP_COMPAT, 3886 }, { 0xFC6C, 0, 2 | DECOMP_COMPAT, 3888 }, { 0xFC6D, 0, 2 | DECOMP_COMPAT, 3890 }, { 0xFC6E, 0, 2 | DECOMP_COMPAT, 3892 }, { 0xFC6F, 0, 2 | DECOMP_COMPAT, 3894 }, { 0xFC70, 0, 2 | DECOMP_COMPAT, 3896 }, { 0xFC71, 0, 2 | DECOMP_COMPAT, 3898 }, { 0xFC72, 0, 2 | DECOMP_COMPAT, 3900 }, { 0xFC73, 0, 2 | DECOMP_COMPAT, 3902 }, { 0xFC74, 0, 2 | DECOMP_COMPAT, 3904 }, { 0xFC75, 0, 2 | DECOMP_COMPAT, 3906 }, { 0xFC76, 0, 2 | DECOMP_COMPAT, 3908 }, { 0xFC77, 0, 2 | DECOMP_COMPAT, 3910 }, { 0xFC78, 0, 2 | DECOMP_COMPAT, 3912 }, { 0xFC79, 0, 2 | DECOMP_COMPAT, 3914 }, { 0xFC7A, 0, 2 | DECOMP_COMPAT, 3916 }, { 0xFC7B, 0, 2 | DECOMP_COMPAT, 3918 }, { 0xFC7C, 0, 2 | DECOMP_COMPAT, 3920 }, { 0xFC7D, 0, 2 | DECOMP_COMPAT, 3922 }, { 0xFC7E, 0, 2 | DECOMP_COMPAT, 3924 }, { 0xFC7F, 0, 2 | DECOMP_COMPAT, 3926 }, { 0xFC80, 0, 2 | DECOMP_COMPAT, 3928 }, { 0xFC81, 0, 2 | DECOMP_COMPAT, 3930 }, { 0xFC82, 0, 2 | DECOMP_COMPAT, 3932 }, { 0xFC83, 0, 2 | DECOMP_COMPAT, 3934 }, { 0xFC84, 0, 2 | DECOMP_COMPAT, 3936 }, { 0xFC85, 0, 2 | DECOMP_COMPAT, 3938 }, { 0xFC86, 0, 2 | DECOMP_COMPAT, 3940 }, { 0xFC87, 0, 2 | DECOMP_COMPAT, 3942 }, { 0xFC88, 0, 2 | DECOMP_COMPAT, 3944 }, { 0xFC89, 0, 2 | DECOMP_COMPAT, 3946 }, { 0xFC8A, 0, 2 | DECOMP_COMPAT, 3948 }, { 0xFC8B, 0, 2 | DECOMP_COMPAT, 3950 }, { 0xFC8C, 0, 2 | DECOMP_COMPAT, 3952 }, { 0xFC8D, 0, 2 | DECOMP_COMPAT, 3954 }, { 0xFC8E, 0, 2 | DECOMP_COMPAT, 3956 }, { 0xFC8F, 0, 2 | DECOMP_COMPAT, 3958 }, { 0xFC90, 0, 2 | DECOMP_COMPAT, 3960 }, { 0xFC91, 0, 2 | DECOMP_COMPAT, 3962 }, { 0xFC92, 0, 2 | DECOMP_COMPAT, 3964 }, { 0xFC93, 0, 2 | DECOMP_COMPAT, 3966 }, { 0xFC94, 0, 2 | DECOMP_COMPAT, 3968 }, { 0xFC95, 0, 2 | DECOMP_COMPAT, 3970 }, { 0xFC96, 0, 2 | DECOMP_COMPAT, 3972 }, { 0xFC97, 0, 2 | DECOMP_COMPAT, 3974 }, { 0xFC98, 0, 2 | DECOMP_COMPAT, 3976 }, { 0xFC99, 0, 2 | DECOMP_COMPAT, 3978 }, { 0xFC9A, 0, 2 | DECOMP_COMPAT, 3980 }, { 0xFC9B, 0, 2 | DECOMP_COMPAT, 3982 }, { 0xFC9C, 0, 2 | DECOMP_COMPAT, 3984 }, { 0xFC9D, 0, 2 | DECOMP_COMPAT, 3986 }, { 0xFC9E, 0, 2 | DECOMP_COMPAT, 3988 }, { 0xFC9F, 0, 2 | DECOMP_COMPAT, 3990 }, { 0xFCA0, 0, 2 | DECOMP_COMPAT, 3992 }, { 0xFCA1, 0, 2 | DECOMP_COMPAT, 3994 }, { 0xFCA2, 0, 2 | DECOMP_COMPAT, 3996 }, { 0xFCA3, 0, 2 | DECOMP_COMPAT, 3998 }, { 0xFCA4, 0, 2 | DECOMP_COMPAT, 4000 }, { 0xFCA5, 0, 2 | DECOMP_COMPAT, 4002 }, { 0xFCA6, 0, 2 | DECOMP_COMPAT, 4004 }, { 0xFCA7, 0, 2 | DECOMP_COMPAT, 4006 }, { 0xFCA8, 0, 2 | DECOMP_COMPAT, 4008 }, { 0xFCA9, 0, 2 | DECOMP_COMPAT, 4010 }, { 0xFCAA, 0, 2 | DECOMP_COMPAT, 4012 }, { 0xFCAB, 0, 2 | DECOMP_COMPAT, 4014 }, { 0xFCAC, 0, 2 | DECOMP_COMPAT, 4016 }, { 0xFCAD, 0, 2 | DECOMP_COMPAT, 4018 }, { 0xFCAE, 0, 2 | DECOMP_COMPAT, 4020 }, { 0xFCAF, 0, 2 | DECOMP_COMPAT, 4022 }, { 0xFCB0, 0, 2 | DECOMP_COMPAT, 4024 }, { 0xFCB1, 0, 2 | DECOMP_COMPAT, 4026 }, { 0xFCB2, 0, 2 | DECOMP_COMPAT, 4028 }, { 0xFCB3, 0, 2 | DECOMP_COMPAT, 4030 }, { 0xFCB4, 0, 2 | DECOMP_COMPAT, 4032 }, { 0xFCB5, 0, 2 | DECOMP_COMPAT, 4034 }, { 0xFCB6, 0, 2 | DECOMP_COMPAT, 4036 }, { 0xFCB7, 0, 2 | DECOMP_COMPAT, 4038 }, { 0xFCB8, 0, 2 | DECOMP_COMPAT, 4040 }, { 0xFCB9, 0, 2 | DECOMP_COMPAT, 4042 }, { 0xFCBA, 0, 2 | DECOMP_COMPAT, 4044 }, { 0xFCBB, 0, 2 | DECOMP_COMPAT, 4046 }, { 0xFCBC, 0, 2 | DECOMP_COMPAT, 4048 }, { 0xFCBD, 0, 2 | DECOMP_COMPAT, 4050 }, { 0xFCBE, 0, 2 | DECOMP_COMPAT, 4052 }, { 0xFCBF, 0, 2 | DECOMP_COMPAT, 4054 }, { 0xFCC0, 0, 2 | DECOMP_COMPAT, 4056 }, { 0xFCC1, 0, 2 | DECOMP_COMPAT, 4058 }, { 0xFCC2, 0, 2 | DECOMP_COMPAT, 4060 }, { 0xFCC3, 0, 2 | DECOMP_COMPAT, 4062 }, { 0xFCC4, 0, 2 | DECOMP_COMPAT, 4064 }, { 0xFCC5, 0, 2 | DECOMP_COMPAT, 4066 }, { 0xFCC6, 0, 2 | DECOMP_COMPAT, 4068 }, { 0xFCC7, 0, 2 | DECOMP_COMPAT, 4070 }, { 0xFCC8, 0, 2 | DECOMP_COMPAT, 4072 }, { 0xFCC9, 0, 2 | DECOMP_COMPAT, 4074 }, { 0xFCCA, 0, 2 | DECOMP_COMPAT, 4076 }, { 0xFCCB, 0, 2 | DECOMP_COMPAT, 4078 }, { 0xFCCC, 0, 2 | DECOMP_COMPAT, 4080 }, { 0xFCCD, 0, 2 | DECOMP_COMPAT, 4082 }, { 0xFCCE, 0, 2 | DECOMP_COMPAT, 4084 }, { 0xFCCF, 0, 2 | DECOMP_COMPAT, 4086 }, { 0xFCD0, 0, 2 | DECOMP_COMPAT, 4088 }, { 0xFCD1, 0, 2 | DECOMP_COMPAT, 4090 }, { 0xFCD2, 0, 2 | DECOMP_COMPAT, 4092 }, { 0xFCD3, 0, 2 | DECOMP_COMPAT, 4094 }, { 0xFCD4, 0, 2 | DECOMP_COMPAT, 4096 }, { 0xFCD5, 0, 2 | DECOMP_COMPAT, 4098 }, { 0xFCD6, 0, 2 | DECOMP_COMPAT, 4100 }, { 0xFCD7, 0, 2 | DECOMP_COMPAT, 4102 }, { 0xFCD8, 0, 2 | DECOMP_COMPAT, 4104 }, { 0xFCD9, 0, 2 | DECOMP_COMPAT, 4106 }, { 0xFCDA, 0, 2 | DECOMP_COMPAT, 4108 }, { 0xFCDB, 0, 2 | DECOMP_COMPAT, 4110 }, { 0xFCDC, 0, 2 | DECOMP_COMPAT, 4112 }, { 0xFCDD, 0, 2 | DECOMP_COMPAT, 4114 }, { 0xFCDE, 0, 2 | DECOMP_COMPAT, 4116 }, { 0xFCDF, 0, 2 | DECOMP_COMPAT, 4118 }, { 0xFCE0, 0, 2 | DECOMP_COMPAT, 4120 }, { 0xFCE1, 0, 2 | DECOMP_COMPAT, 4122 }, { 0xFCE2, 0, 2 | DECOMP_COMPAT, 4124 }, { 0xFCE3, 0, 2 | DECOMP_COMPAT, 4126 }, { 0xFCE4, 0, 2 | DECOMP_COMPAT, 4128 }, { 0xFCE5, 0, 2 | DECOMP_COMPAT, 4130 }, { 0xFCE6, 0, 2 | DECOMP_COMPAT, 4132 }, { 0xFCE7, 0, 2 | DECOMP_COMPAT, 4134 }, { 0xFCE8, 0, 2 | DECOMP_COMPAT, 4136 }, { 0xFCE9, 0, 2 | DECOMP_COMPAT, 4138 }, { 0xFCEA, 0, 2 | DECOMP_COMPAT, 4140 }, { 0xFCEB, 0, 2 | DECOMP_COMPAT, 4142 }, { 0xFCEC, 0, 2 | DECOMP_COMPAT, 4144 }, { 0xFCED, 0, 2 | DECOMP_COMPAT, 4146 }, { 0xFCEE, 0, 2 | DECOMP_COMPAT, 4148 }, { 0xFCEF, 0, 2 | DECOMP_COMPAT, 4150 }, { 0xFCF0, 0, 2 | DECOMP_COMPAT, 4152 }, { 0xFCF1, 0, 2 | DECOMP_COMPAT, 4154 }, { 0xFCF2, 0, 3 | DECOMP_COMPAT, 4156 }, { 0xFCF3, 0, 3 | DECOMP_COMPAT, 4159 }, { 0xFCF4, 0, 3 | DECOMP_COMPAT, 4162 }, { 0xFCF5, 0, 2 | DECOMP_COMPAT, 4165 }, { 0xFCF6, 0, 2 | DECOMP_COMPAT, 4167 }, { 0xFCF7, 0, 2 | DECOMP_COMPAT, 4169 }, { 0xFCF8, 0, 2 | DECOMP_COMPAT, 4171 }, { 0xFCF9, 0, 2 | DECOMP_COMPAT, 4173 }, { 0xFCFA, 0, 2 | DECOMP_COMPAT, 4175 }, { 0xFCFB, 0, 2 | DECOMP_COMPAT, 4177 }, { 0xFCFC, 0, 2 | DECOMP_COMPAT, 4179 }, { 0xFCFD, 0, 2 | DECOMP_COMPAT, 4181 }, { 0xFCFE, 0, 2 | DECOMP_COMPAT, 4183 }, { 0xFCFF, 0, 2 | DECOMP_COMPAT, 4185 }, { 0xFD00, 0, 2 | DECOMP_COMPAT, 4187 }, { 0xFD01, 0, 2 | DECOMP_COMPAT, 4189 }, { 0xFD02, 0, 2 | DECOMP_COMPAT, 4191 }, { 0xFD03, 0, 2 | DECOMP_COMPAT, 4193 }, { 0xFD04, 0, 2 | DECOMP_COMPAT, 4195 }, { 0xFD05, 0, 2 | DECOMP_COMPAT, 4197 }, { 0xFD06, 0, 2 | DECOMP_COMPAT, 4199 }, { 0xFD07, 0, 2 | DECOMP_COMPAT, 4201 }, { 0xFD08, 0, 2 | DECOMP_COMPAT, 4203 }, { 0xFD09, 0, 2 | DECOMP_COMPAT, 4205 }, { 0xFD0A, 0, 2 | DECOMP_COMPAT, 4207 }, { 0xFD0B, 0, 2 | DECOMP_COMPAT, 4209 }, { 0xFD0C, 0, 2 | DECOMP_COMPAT, 4211 }, { 0xFD0D, 0, 2 | DECOMP_COMPAT, 4213 }, { 0xFD0E, 0, 2 | DECOMP_COMPAT, 4215 }, { 0xFD0F, 0, 2 | DECOMP_COMPAT, 4217 }, { 0xFD10, 0, 2 | DECOMP_COMPAT, 4219 }, { 0xFD11, 0, 2 | DECOMP_COMPAT, 4221 }, { 0xFD12, 0, 2 | DECOMP_COMPAT, 4223 }, { 0xFD13, 0, 2 | DECOMP_COMPAT, 4225 }, { 0xFD14, 0, 2 | DECOMP_COMPAT, 4227 }, { 0xFD15, 0, 2 | DECOMP_COMPAT, 4229 }, { 0xFD16, 0, 2 | DECOMP_COMPAT, 4231 }, { 0xFD17, 0, 2 | DECOMP_COMPAT, 4233 }, { 0xFD18, 0, 2 | DECOMP_COMPAT, 4235 }, { 0xFD19, 0, 2 | DECOMP_COMPAT, 4237 }, { 0xFD1A, 0, 2 | DECOMP_COMPAT, 4239 }, { 0xFD1B, 0, 2 | DECOMP_COMPAT, 4241 }, { 0xFD1C, 0, 2 | DECOMP_COMPAT, 4243 }, { 0xFD1D, 0, 2 | DECOMP_COMPAT, 4245 }, { 0xFD1E, 0, 2 | DECOMP_COMPAT, 4247 }, { 0xFD1F, 0, 2 | DECOMP_COMPAT, 4249 }, { 0xFD20, 0, 2 | DECOMP_COMPAT, 4251 }, { 0xFD21, 0, 2 | DECOMP_COMPAT, 4253 }, { 0xFD22, 0, 2 | DECOMP_COMPAT, 4255 }, { 0xFD23, 0, 2 | DECOMP_COMPAT, 4257 }, { 0xFD24, 0, 2 | DECOMP_COMPAT, 4259 }, { 0xFD25, 0, 2 | DECOMP_COMPAT, 4261 }, { 0xFD26, 0, 2 | DECOMP_COMPAT, 4263 }, { 0xFD27, 0, 2 | DECOMP_COMPAT, 4265 }, { 0xFD28, 0, 2 | DECOMP_COMPAT, 4267 }, { 0xFD29, 0, 2 | DECOMP_COMPAT, 4269 }, { 0xFD2A, 0, 2 | DECOMP_COMPAT, 4271 }, { 0xFD2B, 0, 2 | DECOMP_COMPAT, 4273 }, { 0xFD2C, 0, 2 | DECOMP_COMPAT, 4275 }, { 0xFD2D, 0, 2 | DECOMP_COMPAT, 4277 }, { 0xFD2E, 0, 2 | DECOMP_COMPAT, 4279 }, { 0xFD2F, 0, 2 | DECOMP_COMPAT, 4281 }, { 0xFD30, 0, 2 | DECOMP_COMPAT, 4283 }, { 0xFD31, 0, 2 | DECOMP_COMPAT, 4285 }, { 0xFD32, 0, 2 | DECOMP_COMPAT, 4287 }, { 0xFD33, 0, 2 | DECOMP_COMPAT, 4289 }, { 0xFD34, 0, 2 | DECOMP_COMPAT, 4291 }, { 0xFD35, 0, 2 | DECOMP_COMPAT, 4293 }, { 0xFD36, 0, 2 | DECOMP_COMPAT, 4295 }, { 0xFD37, 0, 2 | DECOMP_COMPAT, 4297 }, { 0xFD38, 0, 2 | DECOMP_COMPAT, 4299 }, { 0xFD39, 0, 2 | DECOMP_COMPAT, 4301 }, { 0xFD3A, 0, 2 | DECOMP_COMPAT, 4303 }, { 0xFD3B, 0, 2 | DECOMP_COMPAT, 4305 }, { 0xFD3C, 0, 2 | DECOMP_COMPAT, 4307 }, { 0xFD3D, 0, 2 | DECOMP_COMPAT, 4309 }, { 0xFD50, 0, 3 | DECOMP_COMPAT, 4311 }, { 0xFD51, 0, 3 | DECOMP_COMPAT, 4314 }, { 0xFD52, 0, 3 | DECOMP_COMPAT, 4317 }, { 0xFD53, 0, 3 | DECOMP_COMPAT, 4320 }, { 0xFD54, 0, 3 | DECOMP_COMPAT, 4323 }, { 0xFD55, 0, 3 | DECOMP_COMPAT, 4326 }, { 0xFD56, 0, 3 | DECOMP_COMPAT, 4329 }, { 0xFD57, 0, 3 | DECOMP_COMPAT, 4332 }, { 0xFD58, 0, 3 | DECOMP_COMPAT, 4335 }, { 0xFD59, 0, 3 | DECOMP_COMPAT, 4338 }, { 0xFD5A, 0, 3 | DECOMP_COMPAT, 4341 }, { 0xFD5B, 0, 3 | DECOMP_COMPAT, 4344 }, { 0xFD5C, 0, 3 | DECOMP_COMPAT, 4347 }, { 0xFD5D, 0, 3 | DECOMP_COMPAT, 4350 }, { 0xFD5E, 0, 3 | DECOMP_COMPAT, 4353 }, { 0xFD5F, 0, 3 | DECOMP_COMPAT, 4356 }, { 0xFD60, 0, 3 | DECOMP_COMPAT, 4359 }, { 0xFD61, 0, 3 | DECOMP_COMPAT, 4362 }, { 0xFD62, 0, 3 | DECOMP_COMPAT, 4365 }, { 0xFD63, 0, 3 | DECOMP_COMPAT, 4368 }, { 0xFD64, 0, 3 | DECOMP_COMPAT, 4371 }, { 0xFD65, 0, 3 | DECOMP_COMPAT, 4374 }, { 0xFD66, 0, 3 | DECOMP_COMPAT, 4377 }, { 0xFD67, 0, 3 | DECOMP_COMPAT, 4380 }, { 0xFD68, 0, 3 | DECOMP_COMPAT, 4383 }, { 0xFD69, 0, 3 | DECOMP_COMPAT, 4386 }, { 0xFD6A, 0, 3 | DECOMP_COMPAT, 4389 }, { 0xFD6B, 0, 3 | DECOMP_COMPAT, 4392 }, { 0xFD6C, 0, 3 | DECOMP_COMPAT, 4395 }, { 0xFD6D, 0, 3 | DECOMP_COMPAT, 4398 }, { 0xFD6E, 0, 3 | DECOMP_COMPAT, 4401 }, { 0xFD6F, 0, 3 | DECOMP_COMPAT, 4404 }, { 0xFD70, 0, 3 | DECOMP_COMPAT, 4407 }, { 0xFD71, 0, 3 | DECOMP_COMPAT, 4410 }, { 0xFD72, 0, 3 | DECOMP_COMPAT, 4413 }, { 0xFD73, 0, 3 | DECOMP_COMPAT, 4416 }, { 0xFD74, 0, 3 | DECOMP_COMPAT, 4419 }, { 0xFD75, 0, 3 | DECOMP_COMPAT, 4422 }, { 0xFD76, 0, 3 | DECOMP_COMPAT, 4425 }, { 0xFD77, 0, 3 | DECOMP_COMPAT, 4428 }, { 0xFD78, 0, 3 | DECOMP_COMPAT, 4431 }, { 0xFD79, 0, 3 | DECOMP_COMPAT, 4434 }, { 0xFD7A, 0, 3 | DECOMP_COMPAT, 4437 }, { 0xFD7B, 0, 3 | DECOMP_COMPAT, 4440 }, { 0xFD7C, 0, 3 | DECOMP_COMPAT, 4443 }, { 0xFD7D, 0, 3 | DECOMP_COMPAT, 4446 }, { 0xFD7E, 0, 3 | DECOMP_COMPAT, 4449 }, { 0xFD7F, 0, 3 | DECOMP_COMPAT, 4452 }, { 0xFD80, 0, 3 | DECOMP_COMPAT, 4455 }, { 0xFD81, 0, 3 | DECOMP_COMPAT, 4458 }, { 0xFD82, 0, 3 | DECOMP_COMPAT, 4461 }, { 0xFD83, 0, 3 | DECOMP_COMPAT, 4464 }, { 0xFD84, 0, 3 | DECOMP_COMPAT, 4467 }, { 0xFD85, 0, 3 | DECOMP_COMPAT, 4470 }, { 0xFD86, 0, 3 | DECOMP_COMPAT, 4473 }, { 0xFD87, 0, 3 | DECOMP_COMPAT, 4476 }, { 0xFD88, 0, 3 | DECOMP_COMPAT, 4479 }, { 0xFD89, 0, 3 | DECOMP_COMPAT, 4482 }, { 0xFD8A, 0, 3 | DECOMP_COMPAT, 4485 }, { 0xFD8B, 0, 3 | DECOMP_COMPAT, 4488 }, { 0xFD8C, 0, 3 | DECOMP_COMPAT, 4491 }, { 0xFD8D, 0, 3 | DECOMP_COMPAT, 4494 }, { 0xFD8E, 0, 3 | DECOMP_COMPAT, 4497 }, { 0xFD8F, 0, 3 | DECOMP_COMPAT, 4500 }, { 0xFD92, 0, 3 | DECOMP_COMPAT, 4503 }, { 0xFD93, 0, 3 | DECOMP_COMPAT, 4506 }, { 0xFD94, 0, 3 | DECOMP_COMPAT, 4509 }, { 0xFD95, 0, 3 | DECOMP_COMPAT, 4512 }, { 0xFD96, 0, 3 | DECOMP_COMPAT, 4515 }, { 0xFD97, 0, 3 | DECOMP_COMPAT, 4518 }, { 0xFD98, 0, 3 | DECOMP_COMPAT, 4521 }, { 0xFD99, 0, 3 | DECOMP_COMPAT, 4524 }, { 0xFD9A, 0, 3 | DECOMP_COMPAT, 4527 }, { 0xFD9B, 0, 3 | DECOMP_COMPAT, 4530 }, { 0xFD9C, 0, 3 | DECOMP_COMPAT, 4533 }, { 0xFD9D, 0, 3 | DECOMP_COMPAT, 4536 }, { 0xFD9E, 0, 3 | DECOMP_COMPAT, 4539 }, { 0xFD9F, 0, 3 | DECOMP_COMPAT, 4542 }, { 0xFDA0, 0, 3 | DECOMP_COMPAT, 4545 }, { 0xFDA1, 0, 3 | DECOMP_COMPAT, 4548 }, { 0xFDA2, 0, 3 | DECOMP_COMPAT, 4551 }, { 0xFDA3, 0, 3 | DECOMP_COMPAT, 4554 }, { 0xFDA4, 0, 3 | DECOMP_COMPAT, 4557 }, { 0xFDA5, 0, 3 | DECOMP_COMPAT, 4560 }, { 0xFDA6, 0, 3 | DECOMP_COMPAT, 4563 }, { 0xFDA7, 0, 3 | DECOMP_COMPAT, 4566 }, { 0xFDA8, 0, 3 | DECOMP_COMPAT, 4569 }, { 0xFDA9, 0, 3 | DECOMP_COMPAT, 4572 }, { 0xFDAA, 0, 3 | DECOMP_COMPAT, 4575 }, { 0xFDAB, 0, 3 | DECOMP_COMPAT, 4578 }, { 0xFDAC, 0, 3 | DECOMP_COMPAT, 4581 }, { 0xFDAD, 0, 3 | DECOMP_COMPAT, 4584 }, { 0xFDAE, 0, 3 | DECOMP_COMPAT, 4587 }, { 0xFDAF, 0, 3 | DECOMP_COMPAT, 4590 }, { 0xFDB0, 0, 3 | DECOMP_COMPAT, 4593 }, { 0xFDB1, 0, 3 | DECOMP_COMPAT, 4596 }, { 0xFDB2, 0, 3 | DECOMP_COMPAT, 4599 }, { 0xFDB3, 0, 3 | DECOMP_COMPAT, 4602 }, { 0xFDB4, 0, 3 | DECOMP_COMPAT, 4605 }, { 0xFDB5, 0, 3 | DECOMP_COMPAT, 4608 }, { 0xFDB6, 0, 3 | DECOMP_COMPAT, 4611 }, { 0xFDB7, 0, 3 | DECOMP_COMPAT, 4614 }, { 0xFDB8, 0, 3 | DECOMP_COMPAT, 4617 }, { 0xFDB9, 0, 3 | DECOMP_COMPAT, 4620 }, { 0xFDBA, 0, 3 | DECOMP_COMPAT, 4623 }, { 0xFDBB, 0, 3 | DECOMP_COMPAT, 4626 }, { 0xFDBC, 0, 3 | DECOMP_COMPAT, 4629 }, { 0xFDBD, 0, 3 | DECOMP_COMPAT, 4632 }, { 0xFDBE, 0, 3 | DECOMP_COMPAT, 4635 }, { 0xFDBF, 0, 3 | DECOMP_COMPAT, 4638 }, { 0xFDC0, 0, 3 | DECOMP_COMPAT, 4641 }, { 0xFDC1, 0, 3 | DECOMP_COMPAT, 4644 }, { 0xFDC2, 0, 3 | DECOMP_COMPAT, 4647 }, { 0xFDC3, 0, 3 | DECOMP_COMPAT, 4650 }, { 0xFDC4, 0, 3 | DECOMP_COMPAT, 4653 }, { 0xFDC5, 0, 3 | DECOMP_COMPAT, 4656 }, { 0xFDC6, 0, 3 | DECOMP_COMPAT, 4659 }, { 0xFDC7, 0, 3 | DECOMP_COMPAT, 4662 }, { 0xFDF0, 0, 3 | DECOMP_COMPAT, 4665 }, { 0xFDF1, 0, 3 | DECOMP_COMPAT, 4668 }, { 0xFDF2, 0, 4 | DECOMP_COMPAT, 4671 }, { 0xFDF3, 0, 4 | DECOMP_COMPAT, 4675 }, { 0xFDF4, 0, 4 | DECOMP_COMPAT, 4679 }, { 0xFDF5, 0, 4 | DECOMP_COMPAT, 4683 }, { 0xFDF6, 0, 4 | DECOMP_COMPAT, 4687 }, { 0xFDF7, 0, 4 | DECOMP_COMPAT, 4691 }, { 0xFDF8, 0, 4 | DECOMP_COMPAT, 4695 }, { 0xFDF9, 0, 3 | DECOMP_COMPAT, 4699 }, { 0xFDFA, 0, 18 | DECOMP_COMPAT, 4702 }, { 0xFDFB, 0, 8 | DECOMP_COMPAT, 4720 }, { 0xFDFC, 0, 4 | DECOMP_COMPAT, 4728 }, { 0xFE10, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002C }, { 0xFE11, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3001 }, { 0xFE12, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3002 }, { 0xFE13, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003A }, { 0xFE14, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003B }, { 0xFE15, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0021 }, { 0xFE16, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003F }, { 0xFE17, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3016 }, { 0xFE18, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3017 }, { 0xFE19, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2026 }, { 0xFE20, 230, 0, 0 }, { 0xFE21, 230, 0, 0 }, { 0xFE22, 230, 0, 0 }, { 0xFE23, 230, 0, 0 }, { 0xFE24, 230, 0, 0 }, { 0xFE25, 230, 0, 0 }, { 0xFE26, 230, 0, 0 }, { 0xFE27, 220, 0, 0 }, { 0xFE28, 220, 0, 0 }, { 0xFE29, 220, 0, 0 }, { 0xFE2A, 220, 0, 0 }, { 0xFE2B, 220, 0, 0 }, { 0xFE2C, 220, 0, 0 }, { 0xFE2D, 220, 0, 0 }, { 0xFE2E, 230, 0, 0 }, { 0xFE2F, 230, 0, 0 }, { 0xFE30, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2025 }, { 0xFE31, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2014 }, { 0xFE32, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2013 }, { 0xFE33, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005F }, { 0xFE34, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005F }, { 0xFE35, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0028 }, { 0xFE36, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0029 }, { 0xFE37, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007B }, { 0xFE38, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007D }, { 0xFE39, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3014 }, { 0xFE3A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3015 }, { 0xFE3B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3010 }, { 0xFE3C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3011 }, { 0xFE3D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300A }, { 0xFE3E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300B }, { 0xFE3F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3008 }, { 0xFE40, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3009 }, { 0xFE41, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300C }, { 0xFE42, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300D }, { 0xFE43, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300E }, { 0xFE44, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300F }, { 0xFE47, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005B }, { 0xFE48, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005D }, { 0xFE49, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x203E }, { 0xFE4A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x203E }, { 0xFE4B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x203E }, { 0xFE4C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x203E }, { 0xFE4D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005F }, { 0xFE4E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005F }, { 0xFE4F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005F }, { 0xFE50, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002C }, { 0xFE51, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3001 }, { 0xFE52, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002E }, { 0xFE54, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003B }, { 0xFE55, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003A }, { 0xFE56, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003F }, { 0xFE57, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0021 }, { 0xFE58, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2014 }, { 0xFE59, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0028 }, { 0xFE5A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0029 }, { 0xFE5B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007B }, { 0xFE5C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007D }, { 0xFE5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3014 }, { 0xFE5E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3015 }, { 0xFE5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0023 }, { 0xFE60, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0026 }, { 0xFE61, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002A }, { 0xFE62, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002B }, { 0xFE63, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002D }, { 0xFE64, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003C }, { 0xFE65, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003E }, { 0xFE66, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003D }, { 0xFE68, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005C }, { 0xFE69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0024 }, { 0xFE6A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0025 }, { 0xFE6B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0040 }, { 0xFE70, 0, 2 | DECOMP_COMPAT, 4732 }, { 0xFE71, 0, 2 | DECOMP_COMPAT, 4734 }, { 0xFE72, 0, 2 | DECOMP_COMPAT, 4736 }, { 0xFE74, 0, 2 | DECOMP_COMPAT, 4738 }, { 0xFE76, 0, 2 | DECOMP_COMPAT, 4740 }, { 0xFE77, 0, 2 | DECOMP_COMPAT, 4742 }, { 0xFE78, 0, 2 | DECOMP_COMPAT, 4744 }, { 0xFE79, 0, 2 | DECOMP_COMPAT, 4746 }, { 0xFE7A, 0, 2 | DECOMP_COMPAT, 4748 }, { 0xFE7B, 0, 2 | DECOMP_COMPAT, 4750 }, { 0xFE7C, 0, 2 | DECOMP_COMPAT, 4752 }, { 0xFE7D, 0, 2 | DECOMP_COMPAT, 4754 }, { 0xFE7E, 0, 2 | DECOMP_COMPAT, 4756 }, { 0xFE7F, 0, 2 | DECOMP_COMPAT, 4758 }, { 0xFE80, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0621 }, { 0xFE81, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0622 }, { 0xFE82, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0622 }, { 0xFE83, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0623 }, { 0xFE84, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0623 }, { 0xFE85, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0624 }, { 0xFE86, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0624 }, { 0xFE87, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0625 }, { 0xFE88, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0625 }, { 0xFE89, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0626 }, { 0xFE8A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0626 }, { 0xFE8B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0626 }, { 0xFE8C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0626 }, { 0xFE8D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0627 }, { 0xFE8E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0627 }, { 0xFE8F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0xFE90, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0xFE91, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0xFE92, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0xFE93, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0629 }, { 0xFE94, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0629 }, { 0xFE95, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0xFE96, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0xFE97, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0xFE98, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0xFE99, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0xFE9A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0xFE9B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0xFE9C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0xFE9D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0xFE9E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0xFE9F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0xFEA0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0xFEA1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0xFEA2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0xFEA3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0xFEA4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0xFEA5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0xFEA6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0xFEA7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0xFEA8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0xFEA9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062F }, { 0xFEAA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062F }, { 0xFEAB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0630 }, { 0xFEAC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0630 }, { 0xFEAD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0631 }, { 0xFEAE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0631 }, { 0xFEAF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0632 }, { 0xFEB0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0632 }, { 0xFEB1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0xFEB2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0xFEB3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0xFEB4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0xFEB5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0xFEB6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0xFEB7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0xFEB8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0xFEB9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0xFEBA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0xFEBB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0xFEBC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0xFEBD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0xFEBE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0xFEBF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0xFEC0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0xFEC1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0xFEC2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0xFEC3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0xFEC4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0xFEC5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0xFEC6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0xFEC7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0xFEC8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0xFEC9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0xFECA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0xFECB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0xFECC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0xFECD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0xFECE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0xFECF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0xFED0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0xFED1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0xFED2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0xFED3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0xFED4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0xFED5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0xFED6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0xFED7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0xFED8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0xFED9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0xFEDA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0xFEDB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0xFEDC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0xFEDD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0xFEDE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0xFEDF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0xFEE0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0xFEE1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0xFEE2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0xFEE3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0xFEE4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0xFEE5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0xFEE6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0xFEE7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0xFEE8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0xFEE9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0xFEEA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0xFEEB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0xFEEC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0xFEED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0648 }, { 0xFEEE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0648 }, { 0xFEEF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0649 }, { 0xFEF0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0649 }, { 0xFEF1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0xFEF2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0xFEF3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0xFEF4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0xFEF5, 0, 2 | DECOMP_COMPAT, 4760 }, { 0xFEF6, 0, 2 | DECOMP_COMPAT, 4762 }, { 0xFEF7, 0, 2 | DECOMP_COMPAT, 4764 }, { 0xFEF8, 0, 2 | DECOMP_COMPAT, 4766 }, { 0xFEF9, 0, 2 | DECOMP_COMPAT, 4768 }, { 0xFEFA, 0, 2 | DECOMP_COMPAT, 4770 }, { 0xFEFB, 0, 2 | DECOMP_COMPAT, 4772 }, { 0xFEFC, 0, 2 | DECOMP_COMPAT, 4774 }, { 0xFF01, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0021 }, { 0xFF02, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0022 }, { 0xFF03, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0023 }, { 0xFF04, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0024 }, { 0xFF05, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0025 }, { 0xFF06, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0026 }, { 0xFF07, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0027 }, { 0xFF08, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0028 }, { 0xFF09, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0029 }, { 0xFF0A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002A }, { 0xFF0B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002B }, { 0xFF0C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002C }, { 0xFF0D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002D }, { 0xFF0E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002E }, { 0xFF0F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x002F }, { 0xFF10, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0xFF11, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0xFF12, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0xFF13, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0xFF14, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0xFF15, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0xFF16, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0xFF17, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0xFF18, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0xFF19, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0xFF1A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003A }, { 0xFF1B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003B }, { 0xFF1C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003C }, { 0xFF1D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003D }, { 0xFF1E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003E }, { 0xFF1F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x003F }, { 0xFF20, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0040 }, { 0xFF21, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0xFF22, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0xFF23, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0xFF24, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0xFF25, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0xFF26, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0xFF27, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0xFF28, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0xFF29, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0xFF2A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0xFF2B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0xFF2C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0xFF2D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0xFF2E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0xFF2F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0xFF30, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0xFF31, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0xFF32, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0xFF33, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0xFF34, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0xFF35, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0xFF36, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0xFF37, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0xFF38, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0xFF39, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0xFF3A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0xFF3B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005B }, { 0xFF3C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005C }, { 0xFF3D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005D }, { 0xFF3E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005E }, { 0xFF3F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005F }, { 0xFF40, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0060 }, { 0xFF41, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0xFF42, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0xFF43, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0xFF44, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0xFF45, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0xFF46, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0xFF47, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0xFF48, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0xFF49, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0xFF4A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0xFF4B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0xFF4C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0xFF4D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0xFF4E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0xFF4F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0xFF50, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0xFF51, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0xFF52, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0xFF53, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0xFF54, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0xFF55, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0xFF56, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0xFF57, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0xFF58, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0xFF59, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0xFF5A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0xFF5B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007B }, { 0xFF5C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007C }, { 0xFF5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007D }, { 0xFF5E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007E }, { 0xFF5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2985 }, { 0xFF60, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2986 }, { 0xFF61, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3002 }, { 0xFF62, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300C }, { 0xFF63, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x300D }, { 0xFF64, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3001 }, { 0xFF65, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30FB }, { 0xFF66, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30F2 }, { 0xFF67, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A1 }, { 0xFF68, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A3 }, { 0xFF69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A5 }, { 0xFF6A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A7 }, { 0xFF6B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A9 }, { 0xFF6C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E3 }, { 0xFF6D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E5 }, { 0xFF6E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E7 }, { 0xFF6F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C3 }, { 0xFF70, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30FC }, { 0xFF71, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A2 }, { 0xFF72, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A4 }, { 0xFF73, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A6 }, { 0xFF74, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30A8 }, { 0xFF75, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AA }, { 0xFF76, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AB }, { 0xFF77, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AD }, { 0xFF78, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30AF }, { 0xFF79, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B1 }, { 0xFF7A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B3 }, { 0xFF7B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B5 }, { 0xFF7C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B7 }, { 0xFF7D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B9 }, { 0xFF7E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30BB }, { 0xFF7F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30BD }, { 0xFF80, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30BF }, { 0xFF81, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C1 }, { 0xFF82, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C4 }, { 0xFF83, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C6 }, { 0xFF84, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C8 }, { 0xFF85, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CA }, { 0xFF86, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CB }, { 0xFF87, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CC }, { 0xFF88, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CD }, { 0xFF89, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CE }, { 0xFF8A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30CF }, { 0xFF8B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30D2 }, { 0xFF8C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30D5 }, { 0xFF8D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30D8 }, { 0xFF8E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30DB }, { 0xFF8F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30DE }, { 0xFF90, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30DF }, { 0xFF91, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E0 }, { 0xFF92, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E1 }, { 0xFF93, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E2 }, { 0xFF94, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E4 }, { 0xFF95, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E6 }, { 0xFF96, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E8 }, { 0xFF97, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30E9 }, { 0xFF98, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EA }, { 0xFF99, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EB }, { 0xFF9A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EC }, { 0xFF9B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30ED }, { 0xFF9C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30EF }, { 0xFF9D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30F3 }, { 0xFF9E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3099 }, { 0xFF9F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x309A }, { 0xFFA0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3164 }, { 0xFFA1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3131 }, { 0xFFA2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3132 }, { 0xFFA3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3133 }, { 0xFFA4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3134 }, { 0xFFA5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3135 }, { 0xFFA6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3136 }, { 0xFFA7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3137 }, { 0xFFA8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3138 }, { 0xFFA9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3139 }, { 0xFFAA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x313A }, { 0xFFAB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x313B }, { 0xFFAC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x313C }, { 0xFFAD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x313D }, { 0xFFAE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x313E }, { 0xFFAF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x313F }, { 0xFFB0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3140 }, { 0xFFB1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3141 }, { 0xFFB2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3142 }, { 0xFFB3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3143 }, { 0xFFB4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3144 }, { 0xFFB5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3145 }, { 0xFFB6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3146 }, { 0xFFB7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3147 }, { 0xFFB8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3148 }, { 0xFFB9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3149 }, { 0xFFBA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x314A }, { 0xFFBB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x314B }, { 0xFFBC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x314C }, { 0xFFBD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x314D }, { 0xFFBE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x314E }, { 0xFFC2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x314F }, { 0xFFC3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3150 }, { 0xFFC4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3151 }, { 0xFFC5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3152 }, { 0xFFC6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3153 }, { 0xFFC7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3154 }, { 0xFFCA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3155 }, { 0xFFCB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3156 }, { 0xFFCC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3157 }, { 0xFFCD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3158 }, { 0xFFCE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3159 }, { 0xFFCF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x315A }, { 0xFFD2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x315B }, { 0xFFD3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x315C }, { 0xFFD4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x315D }, { 0xFFD5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x315E }, { 0xFFD6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x315F }, { 0xFFD7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3160 }, { 0xFFDA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3161 }, { 0xFFDB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3162 }, { 0xFFDC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x3163 }, { 0xFFE0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00A2 }, { 0xFFE1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00A3 }, { 0xFFE2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00AC }, { 0xFFE3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00AF }, { 0xFFE4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00A6 }, { 0xFFE5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00A5 }, { 0xFFE6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x20A9 }, { 0xFFE8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2502 }, { 0xFFE9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2190 }, { 0xFFEA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2191 }, { 0xFFEB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2192 }, { 0xFFEC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2193 }, { 0xFFED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x25A0 }, { 0xFFEE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x25CB }, { 0x101FD, 220, 0, 0 }, { 0x102E0, 220, 0, 0 }, { 0x10376, 230, 0, 0 }, { 0x10377, 230, 0, 0 }, { 0x10378, 230, 0, 0 }, { 0x10379, 230, 0, 0 }, { 0x1037A, 230, 0, 0 }, { 0x105C9, 0, 2, 4776 }, { 0x105E4, 0, 2, 4778 }, { 0x10781, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02D0 }, { 0x10782, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02D1 }, { 0x10783, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00E6 }, { 0x10784, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0299 }, { 0x10785, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0253 }, { 0x10787, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A3 }, { 0x10788, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xAB66 }, { 0x10789, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A5 }, { 0x1078A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A4 }, { 0x1078B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0256 }, { 0x1078C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0257 }, { 0x1078D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x1D91 }, { 0x1078E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0258 }, { 0x1078F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x025E }, { 0x10790, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A9 }, { 0x10791, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0264 }, { 0x10792, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0262 }, { 0x10793, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0260 }, { 0x10794, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x029B }, { 0x10795, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0127 }, { 0x10796, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x029C }, { 0x10797, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0267 }, { 0x10798, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0284 }, { 0x10799, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02AA }, { 0x1079A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02AB }, { 0x1079B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x026C }, { 0x1079C, 0, 1 | DECOMP_COMPAT, 4780 }, { 0x1079D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xA78E }, { 0x1079E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x026E }, { 0x1079F, 0, 1 | DECOMP_COMPAT, 4781 }, { 0x107A0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x028E }, { 0x107A1, 0, 1 | DECOMP_COMPAT, 4782 }, { 0x107A2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x00F8 }, { 0x107A3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0276 }, { 0x107A4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0277 }, { 0x107A5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x107A6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x027A }, { 0x107A7, 0, 1 | DECOMP_COMPAT, 4783 }, { 0x107A8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x027D }, { 0x107A9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x027E }, { 0x107AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0280 }, { 0x107AB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A8 }, { 0x107AC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A6 }, { 0x107AD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xAB67 }, { 0x107AE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A7 }, { 0x107AF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0288 }, { 0x107B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2C71 }, { 0x107B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x028F }, { 0x107B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A1 }, { 0x107B4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x02A2 }, { 0x107B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0298 }, { 0x107B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x01C0 }, { 0x107B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x01C1 }, { 0x107B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x01C2 }, { 0x107B9, 0, 1 | DECOMP_COMPAT, 4784 }, { 0x107BA, 0, 1 | DECOMP_COMPAT, 4785 }, { 0x10A0D, 220, 0, 0 }, { 0x10A0F, 230, 0, 0 }, { 0x10A38, 230, 0, 0 }, { 0x10A39, 1, 0, 0 }, { 0x10A3A, 220, 0, 0 }, { 0x10A3F, 9, 0, 0 }, { 0x10AE5, 230, 0, 0 }, { 0x10AE6, 220, 0, 0 }, { 0x10D24, 230, 0, 0 }, { 0x10D25, 230, 0, 0 }, { 0x10D26, 230, 0, 0 }, { 0x10D27, 230, 0, 0 }, { 0x10D69, 230, 0, 0 }, { 0x10D6A, 230, 0, 0 }, { 0x10D6B, 230, 0, 0 }, { 0x10D6C, 230, 0, 0 }, { 0x10D6D, 230, 0, 0 }, { 0x10EAB, 230, 0, 0 }, { 0x10EAC, 230, 0, 0 }, { 0x10EFD, 220, 0, 0 }, { 0x10EFE, 220, 0, 0 }, { 0x10EFF, 220, 0, 0 }, { 0x10F46, 220, 0, 0 }, { 0x10F47, 220, 0, 0 }, { 0x10F48, 230, 0, 0 }, { 0x10F49, 230, 0, 0 }, { 0x10F4A, 230, 0, 0 }, { 0x10F4B, 220, 0, 0 }, { 0x10F4C, 230, 0, 0 }, { 0x10F4D, 220, 0, 0 }, { 0x10F4E, 220, 0, 0 }, { 0x10F4F, 220, 0, 0 }, { 0x10F50, 220, 0, 0 }, { 0x10F82, 230, 0, 0 }, { 0x10F83, 220, 0, 0 }, { 0x10F84, 230, 0, 0 }, { 0x10F85, 220, 0, 0 }, { 0x11046, 9, 0, 0 }, { 0x11070, 9, 0, 0 }, { 0x1107F, 9, 0, 0 }, { 0x1109A, 0, 2, 4786 }, { 0x1109C, 0, 2, 4788 }, { 0x110AB, 0, 2, 4790 }, { 0x110B9, 9, 0, 0 }, { 0x110BA, 7, 0, 0 }, { 0x11100, 230, 0, 0 }, { 0x11101, 230, 0, 0 }, { 0x11102, 230, 0, 0 }, { 0x1112E, 0, 2, 4792 }, { 0x1112F, 0, 2, 4794 }, { 0x11133, 9, 0, 0 }, { 0x11134, 9, 0, 0 }, { 0x11173, 7, 0, 0 }, { 0x111C0, 9, 0, 0 }, { 0x111CA, 7, 0, 0 }, { 0x11235, 9, 0, 0 }, { 0x11236, 7, 0, 0 }, { 0x112E9, 7, 0, 0 }, { 0x112EA, 9, 0, 0 }, { 0x1133B, 7, 0, 0 }, { 0x1133C, 7, 0, 0 }, { 0x1134B, 0, 2, 4796 }, { 0x1134C, 0, 2, 4798 }, { 0x1134D, 9, 0, 0 }, { 0x11366, 230, 0, 0 }, { 0x11367, 230, 0, 0 }, { 0x11368, 230, 0, 0 }, { 0x11369, 230, 0, 0 }, { 0x1136A, 230, 0, 0 }, { 0x1136B, 230, 0, 0 }, { 0x1136C, 230, 0, 0 }, { 0x11370, 230, 0, 0 }, { 0x11371, 230, 0, 0 }, { 0x11372, 230, 0, 0 }, { 0x11373, 230, 0, 0 }, { 0x11374, 230, 0, 0 }, { 0x11383, 0, 2, 4800 }, { 0x11385, 0, 2, 4802 }, { 0x1138E, 0, 2, 4804 }, { 0x11391, 0, 2, 4806 }, { 0x113C5, 0, 2, 4808 }, { 0x113C7, 0, 2, 4810 }, { 0x113C8, 0, 2, 4812 }, { 0x113CE, 9, 0, 0 }, { 0x113CF, 9, 0, 0 }, { 0x113D0, 9, 0, 0 }, { 0x11442, 9, 0, 0 }, { 0x11446, 7, 0, 0 }, { 0x1145E, 230, 0, 0 }, { 0x114BB, 0, 2, 4814 }, { 0x114BC, 0, 2, 4816 }, { 0x114BE, 0, 2, 4818 }, { 0x114C2, 9, 0, 0 }, { 0x114C3, 7, 0, 0 }, { 0x115BA, 0, 2, 4820 }, { 0x115BB, 0, 2, 4822 }, { 0x115BF, 9, 0, 0 }, { 0x115C0, 7, 0, 0 }, { 0x1163F, 9, 0, 0 }, { 0x116B6, 9, 0, 0 }, { 0x116B7, 7, 0, 0 }, { 0x1172B, 9, 0, 0 }, { 0x11839, 9, 0, 0 }, { 0x1183A, 7, 0, 0 }, { 0x11938, 0, 2, 4824 }, { 0x1193D, 9, 0, 0 }, { 0x1193E, 9, 0, 0 }, { 0x11943, 7, 0, 0 }, { 0x119E0, 9, 0, 0 }, { 0x11A34, 9, 0, 0 }, { 0x11A47, 9, 0, 0 }, { 0x11A99, 9, 0, 0 }, { 0x11C3F, 9, 0, 0 }, { 0x11D42, 7, 0, 0 }, { 0x11D44, 9, 0, 0 }, { 0x11D45, 9, 0, 0 }, { 0x11D97, 9, 0, 0 }, { 0x11F41, 9, 0, 0 }, { 0x11F42, 9, 0, 0 }, { 0x16121, 0, 2, 4826 }, { 0x16122, 0, 2, 4828 }, { 0x16123, 0, 2, 4830 }, { 0x16124, 0, 2, 4832 }, { 0x16125, 0, 2, 4834 }, { 0x16126, 0, 2, 4836 }, { 0x16127, 0, 2, 4838 }, { 0x16128, 0, 2, 4840 }, { 0x1612F, 9, 0, 0 }, { 0x16AF0, 1, 0, 0 }, { 0x16AF1, 1, 0, 0 }, { 0x16AF2, 1, 0, 0 }, { 0x16AF3, 1, 0, 0 }, { 0x16AF4, 1, 0, 0 }, { 0x16B30, 230, 0, 0 }, { 0x16B31, 230, 0, 0 }, { 0x16B32, 230, 0, 0 }, { 0x16B33, 230, 0, 0 }, { 0x16B34, 230, 0, 0 }, { 0x16B35, 230, 0, 0 }, { 0x16B36, 230, 0, 0 }, { 0x16D68, 0, 2, 4842 }, { 0x16D69, 0, 2, 4844 }, { 0x16D6A, 0, 2, 4846 }, { 0x16FF0, 6, 0, 0 }, { 0x16FF1, 6, 0, 0 }, { 0x1BC9E, 1, 0, 0 }, { 0x1CCD6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1CCD7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1CCD8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1CCD9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1CCDA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1CCDB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1CCDC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1CCDD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1CCDE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1CCDF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1CCE0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1CCE1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1CCE2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1CCE3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1CCE4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1CCE5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1CCE6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1CCE7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1CCE8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1CCE9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1CCEA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1CCEB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1CCEC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1CCED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1CCEE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1CCEF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1CCF0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1CCF1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1CCF2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1CCF3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1CCF4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1CCF5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1CCF6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1CCF7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1CCF8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1CCF9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x1D15E, 0, 2 | DECOMP_NO_COMPOSE, 4848 }, /* in exclusion list */ { 0x1D15F, 0, 2 | DECOMP_NO_COMPOSE, 4850 }, /* in exclusion list */ { 0x1D160, 0, 2 | DECOMP_NO_COMPOSE, 4852 }, /* in exclusion list */ { 0x1D161, 0, 2 | DECOMP_NO_COMPOSE, 4854 }, /* in exclusion list */ { 0x1D162, 0, 2 | DECOMP_NO_COMPOSE, 4856 }, /* in exclusion list */ { 0x1D163, 0, 2 | DECOMP_NO_COMPOSE, 4858 }, /* in exclusion list */ { 0x1D164, 0, 2 | DECOMP_NO_COMPOSE, 4860 }, /* in exclusion list */ { 0x1D165, 216, 0, 0 }, { 0x1D166, 216, 0, 0 }, { 0x1D167, 1, 0, 0 }, { 0x1D168, 1, 0, 0 }, { 0x1D169, 1, 0, 0 }, { 0x1D16D, 226, 0, 0 }, { 0x1D16E, 216, 0, 0 }, { 0x1D16F, 216, 0, 0 }, { 0x1D170, 216, 0, 0 }, { 0x1D171, 216, 0, 0 }, { 0x1D172, 216, 0, 0 }, { 0x1D17B, 220, 0, 0 }, { 0x1D17C, 220, 0, 0 }, { 0x1D17D, 220, 0, 0 }, { 0x1D17E, 220, 0, 0 }, { 0x1D17F, 220, 0, 0 }, { 0x1D180, 220, 0, 0 }, { 0x1D181, 220, 0, 0 }, { 0x1D182, 220, 0, 0 }, { 0x1D185, 230, 0, 0 }, { 0x1D186, 230, 0, 0 }, { 0x1D187, 230, 0, 0 }, { 0x1D188, 230, 0, 0 }, { 0x1D189, 230, 0, 0 }, { 0x1D18A, 220, 0, 0 }, { 0x1D18B, 220, 0, 0 }, { 0x1D1AA, 230, 0, 0 }, { 0x1D1AB, 230, 0, 0 }, { 0x1D1AC, 230, 0, 0 }, { 0x1D1AD, 230, 0, 0 }, { 0x1D1BB, 0, 2 | DECOMP_NO_COMPOSE, 4862 }, /* in exclusion list */ { 0x1D1BC, 0, 2 | DECOMP_NO_COMPOSE, 4864 }, /* in exclusion list */ { 0x1D1BD, 0, 2 | DECOMP_NO_COMPOSE, 4866 }, /* in exclusion list */ { 0x1D1BE, 0, 2 | DECOMP_NO_COMPOSE, 4868 }, /* in exclusion list */ { 0x1D1BF, 0, 2 | DECOMP_NO_COMPOSE, 4870 }, /* in exclusion list */ { 0x1D1C0, 0, 2 | DECOMP_NO_COMPOSE, 4872 }, /* in exclusion list */ { 0x1D242, 230, 0, 0 }, { 0x1D243, 230, 0, 0 }, { 0x1D244, 230, 0, 0 }, { 0x1D400, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D401, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D402, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D403, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D404, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D405, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D406, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D407, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D408, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D409, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D40A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D40B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D40C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D40D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D40E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D40F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D410, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D411, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D412, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D413, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D414, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D415, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D416, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D417, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D418, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D419, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D41A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D41B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D41C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D41D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D41E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D41F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D420, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D421, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D422, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D423, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D424, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D425, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D426, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D427, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D428, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D429, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D42A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D42B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D42C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D42D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D42E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D42F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D430, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D431, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D432, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D433, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D434, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D435, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D436, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D437, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D438, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D439, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D43A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D43B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D43C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D43D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D43E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D43F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D440, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D441, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D442, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D443, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D444, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D445, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D446, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D447, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D448, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D449, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D44A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D44B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D44C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D44D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D44E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D44F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D450, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D451, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D452, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D453, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D454, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D456, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D457, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D458, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D459, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D45A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D45B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D45C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D45D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D45E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D45F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D460, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D461, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D462, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D463, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D464, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D465, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D466, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D467, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D468, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D469, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D46A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D46B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D46C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D46D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D46E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D46F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D470, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D471, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D472, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D473, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D474, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D475, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D476, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D477, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D478, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D479, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D47A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D47B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D47C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D47D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D47E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D47F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D480, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D481, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D482, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D483, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D484, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D485, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D486, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D487, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D488, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D489, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D48A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D48B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D48C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D48D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D48E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D48F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D490, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D491, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D492, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D493, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D494, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D495, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D496, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D497, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D498, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D499, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D49A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D49B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D49C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D49E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D49F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D4A2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D4A5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D4A6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D4A9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D4AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D4AB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D4AC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D4AE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D4AF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D4B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D4B1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D4B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D4B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D4B4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D4B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D4B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D4B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D4B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D4B9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D4BB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D4BD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D4BE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D4BF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D4C0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D4C1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D4C2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D4C3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D4C5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D4C6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D4C7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D4C8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D4C9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D4CA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D4CB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D4CC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D4CD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D4CE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D4CF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D4D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D4D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D4D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D4D3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D4D4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D4D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D4D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D4D7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D4D8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D4D9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D4DA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D4DB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D4DC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D4DD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D4DE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D4DF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D4E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D4E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D4E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D4E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D4E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D4E5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D4E6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D4E7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D4E8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D4E9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D4EA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D4EB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D4EC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D4ED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D4EE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D4EF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D4F0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D4F1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D4F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D4F3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D4F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D4F5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D4F6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D4F7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D4F8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D4F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D4FA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D4FB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D4FC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D4FD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D4FE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D4FF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D500, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D501, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D502, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D503, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D504, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D505, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D507, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D508, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D509, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D50A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D50D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D50E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D50F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D510, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D511, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D512, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D513, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D514, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D516, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D517, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D518, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D519, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D51A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D51B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D51C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D51E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D51F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D520, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D521, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D522, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D523, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D524, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D525, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D526, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D527, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D528, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D529, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D52A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D52B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D52C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D52D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D52E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D52F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D530, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D531, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D532, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D533, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D534, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D535, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D536, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D537, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D538, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D539, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D53B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D53C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D53D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D53E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D540, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D541, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D542, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D543, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D544, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D546, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D54A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D54B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D54C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D54D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D54E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D54F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D550, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D552, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D553, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D554, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D555, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D556, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D557, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D558, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D559, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D55A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D55B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D55C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D55D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D55E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D55F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D560, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D561, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D562, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D563, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D564, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D565, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D566, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D567, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D568, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D569, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D56A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D56B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D56C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D56D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D56E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D56F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D570, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D571, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D572, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D573, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D574, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D575, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D576, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D577, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D578, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D579, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D57A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D57B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D57C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D57D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D57E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D57F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D580, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D581, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D582, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D583, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D584, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D585, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D586, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D587, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D588, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D589, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D58A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D58B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D58C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D58D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D58E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D58F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D590, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D591, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D592, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D593, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D594, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D595, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D596, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D597, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D598, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D599, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D59A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D59B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D59C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D59D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D59E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D59F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D5A0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D5A1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D5A2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D5A3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D5A4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D5A5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D5A6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D5A7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D5A8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D5A9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D5AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D5AB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D5AC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D5AD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D5AE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D5AF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D5B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D5B1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D5B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D5B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D5B4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D5B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D5B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D5B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D5B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D5B9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D5BA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D5BB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D5BC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D5BD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D5BE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D5BF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D5C0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D5C1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D5C2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D5C3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D5C4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D5C5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D5C6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D5C7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D5C8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D5C9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D5CA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D5CB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D5CC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D5CD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D5CE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D5CF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D5D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D5D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D5D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D5D3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D5D4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D5D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D5D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D5D7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D5D8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D5D9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D5DA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D5DB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D5DC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D5DD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D5DE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D5DF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D5E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D5E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D5E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D5E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D5E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D5E5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D5E6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D5E7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D5E8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D5E9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D5EA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D5EB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D5EC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D5ED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D5EE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D5EF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D5F0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D5F1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D5F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D5F3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D5F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D5F5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D5F6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D5F7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D5F8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D5F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D5FA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D5FB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D5FC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D5FD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D5FE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D5FF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D600, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D601, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D602, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D603, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D604, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D605, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D606, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D607, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D608, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D609, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D60A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D60B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D60C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D60D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D60E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D60F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D610, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D611, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D612, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D613, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D614, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D615, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D616, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D617, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D618, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D619, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D61A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D61B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D61C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D61D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D61E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D61F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D620, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D621, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D622, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D623, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D624, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D625, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D626, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D627, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D628, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D629, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D62A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D62B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D62C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D62D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D62E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D62F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D630, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D631, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D632, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D633, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D634, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D635, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D636, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D637, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D638, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D639, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D63A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D63B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D63C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D63D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D63E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D63F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D640, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D641, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D642, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D643, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D644, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D645, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D646, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D647, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D648, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D649, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D64A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D64B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D64C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D64D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D64E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D64F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D650, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D651, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D652, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D653, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D654, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D655, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D656, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D657, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D658, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D659, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D65A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D65B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D65C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D65D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D65E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D65F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D660, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D661, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D662, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D663, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D664, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D665, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D666, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D667, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D668, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D669, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D66A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D66B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D66C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D66D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D66E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D66F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D670, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1D671, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1D672, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1D673, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1D674, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1D675, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1D676, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1D677, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1D678, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1D679, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1D67A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1D67B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1D67C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1D67D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1D67E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1D67F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1D680, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1D681, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1D682, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1D683, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1D684, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1D685, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1D686, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1D687, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1D688, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1D689, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1D68A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0061 }, { 0x1D68B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0062 }, { 0x1D68C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0063 }, { 0x1D68D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0064 }, { 0x1D68E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0065 }, { 0x1D68F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0066 }, { 0x1D690, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0067 }, { 0x1D691, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0068 }, { 0x1D692, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0069 }, { 0x1D693, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006A }, { 0x1D694, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006B }, { 0x1D695, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006C }, { 0x1D696, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006D }, { 0x1D697, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006E }, { 0x1D698, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x006F }, { 0x1D699, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0070 }, { 0x1D69A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0071 }, { 0x1D69B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0072 }, { 0x1D69C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0073 }, { 0x1D69D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0074 }, { 0x1D69E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0075 }, { 0x1D69F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0076 }, { 0x1D6A0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0077 }, { 0x1D6A1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0078 }, { 0x1D6A2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0079 }, { 0x1D6A3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x007A }, { 0x1D6A4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0131 }, { 0x1D6A5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0237 }, { 0x1D6A8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0391 }, { 0x1D6A9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0392 }, { 0x1D6AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0393 }, { 0x1D6AB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0394 }, { 0x1D6AC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0395 }, { 0x1D6AD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0396 }, { 0x1D6AE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0397 }, { 0x1D6AF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0398 }, { 0x1D6B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0399 }, { 0x1D6B1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039A }, { 0x1D6B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039B }, { 0x1D6B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039C }, { 0x1D6B4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039D }, { 0x1D6B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039E }, { 0x1D6B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039F }, { 0x1D6B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A0 }, { 0x1D6B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A1 }, { 0x1D6B9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F4 }, { 0x1D6BA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A3 }, { 0x1D6BB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A4 }, { 0x1D6BC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A5 }, { 0x1D6BD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A6 }, { 0x1D6BE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A7 }, { 0x1D6BF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A8 }, { 0x1D6C0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A9 }, { 0x1D6C1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2207 }, { 0x1D6C2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B1 }, { 0x1D6C3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D6C4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D6C5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B4 }, { 0x1D6C6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B5 }, { 0x1D6C7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B6 }, { 0x1D6C8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B7 }, { 0x1D6C9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x1D6CA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B9 }, { 0x1D6CB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BA }, { 0x1D6CC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BB }, { 0x1D6CD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BC }, { 0x1D6CE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BD }, { 0x1D6CF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BE }, { 0x1D6D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BF }, { 0x1D6D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x1D6D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x1D6D3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C2 }, { 0x1D6D4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C3 }, { 0x1D6D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C4 }, { 0x1D6D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C5 }, { 0x1D6D7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D6D8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D6D9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C8 }, { 0x1D6DA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C9 }, { 0x1D6DB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2202 }, { 0x1D6DC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F5 }, { 0x1D6DD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D1 }, { 0x1D6DE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F0 }, { 0x1D6DF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D5 }, { 0x1D6E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F1 }, { 0x1D6E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D6 }, { 0x1D6E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0391 }, { 0x1D6E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0392 }, { 0x1D6E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0393 }, { 0x1D6E5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0394 }, { 0x1D6E6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0395 }, { 0x1D6E7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0396 }, { 0x1D6E8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0397 }, { 0x1D6E9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0398 }, { 0x1D6EA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0399 }, { 0x1D6EB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039A }, { 0x1D6EC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039B }, { 0x1D6ED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039C }, { 0x1D6EE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039D }, { 0x1D6EF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039E }, { 0x1D6F0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039F }, { 0x1D6F1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A0 }, { 0x1D6F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A1 }, { 0x1D6F3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F4 }, { 0x1D6F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A3 }, { 0x1D6F5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A4 }, { 0x1D6F6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A5 }, { 0x1D6F7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A6 }, { 0x1D6F8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A7 }, { 0x1D6F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A8 }, { 0x1D6FA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A9 }, { 0x1D6FB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2207 }, { 0x1D6FC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B1 }, { 0x1D6FD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D6FE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D6FF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B4 }, { 0x1D700, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B5 }, { 0x1D701, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B6 }, { 0x1D702, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B7 }, { 0x1D703, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x1D704, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B9 }, { 0x1D705, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BA }, { 0x1D706, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BB }, { 0x1D707, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BC }, { 0x1D708, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BD }, { 0x1D709, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BE }, { 0x1D70A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BF }, { 0x1D70B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x1D70C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x1D70D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C2 }, { 0x1D70E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C3 }, { 0x1D70F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C4 }, { 0x1D710, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C5 }, { 0x1D711, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D712, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D713, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C8 }, { 0x1D714, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C9 }, { 0x1D715, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2202 }, { 0x1D716, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F5 }, { 0x1D717, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D1 }, { 0x1D718, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F0 }, { 0x1D719, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D5 }, { 0x1D71A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F1 }, { 0x1D71B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D6 }, { 0x1D71C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0391 }, { 0x1D71D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0392 }, { 0x1D71E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0393 }, { 0x1D71F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0394 }, { 0x1D720, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0395 }, { 0x1D721, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0396 }, { 0x1D722, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0397 }, { 0x1D723, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0398 }, { 0x1D724, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0399 }, { 0x1D725, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039A }, { 0x1D726, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039B }, { 0x1D727, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039C }, { 0x1D728, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039D }, { 0x1D729, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039E }, { 0x1D72A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039F }, { 0x1D72B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A0 }, { 0x1D72C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A1 }, { 0x1D72D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F4 }, { 0x1D72E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A3 }, { 0x1D72F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A4 }, { 0x1D730, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A5 }, { 0x1D731, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A6 }, { 0x1D732, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A7 }, { 0x1D733, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A8 }, { 0x1D734, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A9 }, { 0x1D735, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2207 }, { 0x1D736, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B1 }, { 0x1D737, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D738, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D739, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B4 }, { 0x1D73A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B5 }, { 0x1D73B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B6 }, { 0x1D73C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B7 }, { 0x1D73D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x1D73E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B9 }, { 0x1D73F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BA }, { 0x1D740, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BB }, { 0x1D741, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BC }, { 0x1D742, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BD }, { 0x1D743, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BE }, { 0x1D744, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BF }, { 0x1D745, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x1D746, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x1D747, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C2 }, { 0x1D748, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C3 }, { 0x1D749, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C4 }, { 0x1D74A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C5 }, { 0x1D74B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D74C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D74D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C8 }, { 0x1D74E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C9 }, { 0x1D74F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2202 }, { 0x1D750, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F5 }, { 0x1D751, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D1 }, { 0x1D752, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F0 }, { 0x1D753, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D5 }, { 0x1D754, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F1 }, { 0x1D755, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D6 }, { 0x1D756, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0391 }, { 0x1D757, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0392 }, { 0x1D758, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0393 }, { 0x1D759, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0394 }, { 0x1D75A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0395 }, { 0x1D75B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0396 }, { 0x1D75C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0397 }, { 0x1D75D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0398 }, { 0x1D75E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0399 }, { 0x1D75F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039A }, { 0x1D760, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039B }, { 0x1D761, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039C }, { 0x1D762, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039D }, { 0x1D763, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039E }, { 0x1D764, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039F }, { 0x1D765, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A0 }, { 0x1D766, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A1 }, { 0x1D767, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F4 }, { 0x1D768, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A3 }, { 0x1D769, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A4 }, { 0x1D76A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A5 }, { 0x1D76B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A6 }, { 0x1D76C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A7 }, { 0x1D76D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A8 }, { 0x1D76E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A9 }, { 0x1D76F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2207 }, { 0x1D770, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B1 }, { 0x1D771, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D772, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D773, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B4 }, { 0x1D774, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B5 }, { 0x1D775, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B6 }, { 0x1D776, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B7 }, { 0x1D777, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x1D778, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B9 }, { 0x1D779, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BA }, { 0x1D77A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BB }, { 0x1D77B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BC }, { 0x1D77C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BD }, { 0x1D77D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BE }, { 0x1D77E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BF }, { 0x1D77F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x1D780, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x1D781, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C2 }, { 0x1D782, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C3 }, { 0x1D783, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C4 }, { 0x1D784, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C5 }, { 0x1D785, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D786, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D787, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C8 }, { 0x1D788, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C9 }, { 0x1D789, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2202 }, { 0x1D78A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F5 }, { 0x1D78B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D1 }, { 0x1D78C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F0 }, { 0x1D78D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D5 }, { 0x1D78E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F1 }, { 0x1D78F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D6 }, { 0x1D790, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0391 }, { 0x1D791, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0392 }, { 0x1D792, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0393 }, { 0x1D793, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0394 }, { 0x1D794, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0395 }, { 0x1D795, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0396 }, { 0x1D796, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0397 }, { 0x1D797, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0398 }, { 0x1D798, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0399 }, { 0x1D799, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039A }, { 0x1D79A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039B }, { 0x1D79B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039C }, { 0x1D79C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039D }, { 0x1D79D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039E }, { 0x1D79E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x039F }, { 0x1D79F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A0 }, { 0x1D7A0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A1 }, { 0x1D7A1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F4 }, { 0x1D7A2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A3 }, { 0x1D7A3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A4 }, { 0x1D7A4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A5 }, { 0x1D7A5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A6 }, { 0x1D7A6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A7 }, { 0x1D7A7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A8 }, { 0x1D7A8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03A9 }, { 0x1D7A9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2207 }, { 0x1D7AA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B1 }, { 0x1D7AB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B2 }, { 0x1D7AC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B3 }, { 0x1D7AD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B4 }, { 0x1D7AE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B5 }, { 0x1D7AF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B6 }, { 0x1D7B0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B7 }, { 0x1D7B1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B8 }, { 0x1D7B2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03B9 }, { 0x1D7B3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BA }, { 0x1D7B4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BB }, { 0x1D7B5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BC }, { 0x1D7B6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BD }, { 0x1D7B7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BE }, { 0x1D7B8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03BF }, { 0x1D7B9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C0 }, { 0x1D7BA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C1 }, { 0x1D7BB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C2 }, { 0x1D7BC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C3 }, { 0x1D7BD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C4 }, { 0x1D7BE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C5 }, { 0x1D7BF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C6 }, { 0x1D7C0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C7 }, { 0x1D7C1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C8 }, { 0x1D7C2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03C9 }, { 0x1D7C3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x2202 }, { 0x1D7C4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F5 }, { 0x1D7C5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D1 }, { 0x1D7C6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F0 }, { 0x1D7C7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D5 }, { 0x1D7C8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03F1 }, { 0x1D7C9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03D6 }, { 0x1D7CA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03DC }, { 0x1D7CB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x03DD }, { 0x1D7CE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1D7CF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1D7D0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1D7D1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1D7D2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1D7D3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1D7D4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1D7D5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1D7D6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1D7D7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x1D7D8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1D7D9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1D7DA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1D7DB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1D7DC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1D7DD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1D7DE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1D7DF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1D7E0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1D7E1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x1D7E2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1D7E3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1D7E4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1D7E5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1D7E6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1D7E7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1D7E8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1D7E9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1D7EA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1D7EB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x1D7EC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1D7ED, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1D7EE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1D7EF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1D7F0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1D7F1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1D7F2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1D7F3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1D7F4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1D7F5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x1D7F6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1D7F7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1D7F8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1D7F9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1D7FA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1D7FB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1D7FC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1D7FD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1D7FE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1D7FF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x1E000, 230, 0, 0 }, { 0x1E001, 230, 0, 0 }, { 0x1E002, 230, 0, 0 }, { 0x1E003, 230, 0, 0 }, { 0x1E004, 230, 0, 0 }, { 0x1E005, 230, 0, 0 }, { 0x1E006, 230, 0, 0 }, { 0x1E008, 230, 0, 0 }, { 0x1E009, 230, 0, 0 }, { 0x1E00A, 230, 0, 0 }, { 0x1E00B, 230, 0, 0 }, { 0x1E00C, 230, 0, 0 }, { 0x1E00D, 230, 0, 0 }, { 0x1E00E, 230, 0, 0 }, { 0x1E00F, 230, 0, 0 }, { 0x1E010, 230, 0, 0 }, { 0x1E011, 230, 0, 0 }, { 0x1E012, 230, 0, 0 }, { 0x1E013, 230, 0, 0 }, { 0x1E014, 230, 0, 0 }, { 0x1E015, 230, 0, 0 }, { 0x1E016, 230, 0, 0 }, { 0x1E017, 230, 0, 0 }, { 0x1E018, 230, 0, 0 }, { 0x1E01B, 230, 0, 0 }, { 0x1E01C, 230, 0, 0 }, { 0x1E01D, 230, 0, 0 }, { 0x1E01E, 230, 0, 0 }, { 0x1E01F, 230, 0, 0 }, { 0x1E020, 230, 0, 0 }, { 0x1E021, 230, 0, 0 }, { 0x1E023, 230, 0, 0 }, { 0x1E024, 230, 0, 0 }, { 0x1E026, 230, 0, 0 }, { 0x1E027, 230, 0, 0 }, { 0x1E028, 230, 0, 0 }, { 0x1E029, 230, 0, 0 }, { 0x1E02A, 230, 0, 0 }, { 0x1E030, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0430 }, { 0x1E031, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0431 }, { 0x1E032, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0432 }, { 0x1E033, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0433 }, { 0x1E034, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0434 }, { 0x1E035, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0435 }, { 0x1E036, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0436 }, { 0x1E037, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0437 }, { 0x1E038, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0438 }, { 0x1E039, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043A }, { 0x1E03A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043B }, { 0x1E03B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043C }, { 0x1E03C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043E }, { 0x1E03D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043F }, { 0x1E03E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0440 }, { 0x1E03F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0441 }, { 0x1E040, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0442 }, { 0x1E041, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0443 }, { 0x1E042, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0444 }, { 0x1E043, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0445 }, { 0x1E044, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0446 }, { 0x1E045, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0447 }, { 0x1E046, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0448 }, { 0x1E047, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044B }, { 0x1E048, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044D }, { 0x1E049, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044E }, { 0x1E04A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xA689 }, { 0x1E04B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x04D9 }, { 0x1E04C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0456 }, { 0x1E04D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0458 }, { 0x1E04E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x04E9 }, { 0x1E04F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x04AF }, { 0x1E050, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x04CF }, { 0x1E051, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0430 }, { 0x1E052, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0431 }, { 0x1E053, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0432 }, { 0x1E054, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0433 }, { 0x1E055, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0434 }, { 0x1E056, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0435 }, { 0x1E057, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0436 }, { 0x1E058, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0437 }, { 0x1E059, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0438 }, { 0x1E05A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043A }, { 0x1E05B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043B }, { 0x1E05C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043E }, { 0x1E05D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x043F }, { 0x1E05E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0441 }, { 0x1E05F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0443 }, { 0x1E060, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0444 }, { 0x1E061, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0445 }, { 0x1E062, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0446 }, { 0x1E063, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0447 }, { 0x1E064, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0448 }, { 0x1E065, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044A }, { 0x1E066, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x044B }, { 0x1E067, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0491 }, { 0x1E068, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0456 }, { 0x1E069, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0455 }, { 0x1E06A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x045F }, { 0x1E06B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x04AB }, { 0x1E06C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0xA651 }, { 0x1E06D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x04B1 }, { 0x1E08F, 230, 0, 0 }, { 0x1E130, 230, 0, 0 }, { 0x1E131, 230, 0, 0 }, { 0x1E132, 230, 0, 0 }, { 0x1E133, 230, 0, 0 }, { 0x1E134, 230, 0, 0 }, { 0x1E135, 230, 0, 0 }, { 0x1E136, 230, 0, 0 }, { 0x1E2AE, 230, 0, 0 }, { 0x1E2EC, 230, 0, 0 }, { 0x1E2ED, 230, 0, 0 }, { 0x1E2EE, 230, 0, 0 }, { 0x1E2EF, 230, 0, 0 }, { 0x1E4EC, 232, 0, 0 }, { 0x1E4ED, 232, 0, 0 }, { 0x1E4EE, 220, 0, 0 }, { 0x1E4EF, 230, 0, 0 }, { 0x1E5EE, 230, 0, 0 }, { 0x1E5EF, 220, 0, 0 }, { 0x1E8D0, 220, 0, 0 }, { 0x1E8D1, 220, 0, 0 }, { 0x1E8D2, 220, 0, 0 }, { 0x1E8D3, 220, 0, 0 }, { 0x1E8D4, 220, 0, 0 }, { 0x1E8D5, 220, 0, 0 }, { 0x1E8D6, 220, 0, 0 }, { 0x1E944, 230, 0, 0 }, { 0x1E945, 230, 0, 0 }, { 0x1E946, 230, 0, 0 }, { 0x1E947, 230, 0, 0 }, { 0x1E948, 230, 0, 0 }, { 0x1E949, 230, 0, 0 }, { 0x1E94A, 7, 0, 0 }, { 0x1EE00, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0627 }, { 0x1EE01, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0x1EE02, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0x1EE03, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062F }, { 0x1EE05, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0648 }, { 0x1EE06, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0632 }, { 0x1EE07, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0x1EE08, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0x1EE09, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0x1EE0A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0x1EE0B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0x1EE0C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0x1EE0D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0x1EE0E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0x1EE0F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0x1EE10, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0x1EE11, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0x1EE12, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0x1EE13, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0631 }, { 0x1EE14, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0x1EE15, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0x1EE16, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0x1EE17, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0x1EE18, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0630 }, { 0x1EE19, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0x1EE1A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0x1EE1B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0x1EE1C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x066E }, { 0x1EE1D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BA }, { 0x1EE1E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A1 }, { 0x1EE1F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x066F }, { 0x1EE21, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0x1EE22, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0x1EE24, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0x1EE27, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0x1EE29, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0x1EE2A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0x1EE2B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0x1EE2C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0x1EE2D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0x1EE2E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0x1EE2F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0x1EE30, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0x1EE31, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0x1EE32, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0x1EE34, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0x1EE35, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0x1EE36, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0x1EE37, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0x1EE39, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0x1EE3B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0x1EE42, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0x1EE47, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0x1EE49, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0x1EE4B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0x1EE4D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0x1EE4E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0x1EE4F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0x1EE51, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0x1EE52, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0x1EE54, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0x1EE57, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0x1EE59, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0x1EE5B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0x1EE5D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06BA }, { 0x1EE5F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x066F }, { 0x1EE61, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0x1EE62, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0x1EE64, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0x1EE67, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0x1EE68, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0x1EE69, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0x1EE6A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0643 }, { 0x1EE6C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0x1EE6D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0x1EE6E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0x1EE6F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0x1EE70, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0x1EE71, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0x1EE72, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0x1EE74, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0x1EE75, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0x1EE76, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0x1EE77, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0x1EE79, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0x1EE7A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0x1EE7B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0x1EE7C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x066E }, { 0x1EE7E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x06A1 }, { 0x1EE80, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0627 }, { 0x1EE81, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0x1EE82, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0x1EE83, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062F }, { 0x1EE84, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0647 }, { 0x1EE85, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0648 }, { 0x1EE86, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0632 }, { 0x1EE87, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0x1EE88, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0x1EE89, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0x1EE8B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0x1EE8C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0x1EE8D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0x1EE8E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0x1EE8F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0x1EE90, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0x1EE91, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0x1EE92, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0x1EE93, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0631 }, { 0x1EE94, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0x1EE95, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0x1EE96, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0x1EE97, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0x1EE98, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0630 }, { 0x1EE99, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0x1EE9A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0x1EE9B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0x1EEA1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0628 }, { 0x1EEA2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062C }, { 0x1EEA3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062F }, { 0x1EEA5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0648 }, { 0x1EEA6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0632 }, { 0x1EEA7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062D }, { 0x1EEA8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0637 }, { 0x1EEA9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x064A }, { 0x1EEAB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0644 }, { 0x1EEAC, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0645 }, { 0x1EEAD, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0646 }, { 0x1EEAE, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0633 }, { 0x1EEAF, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0639 }, { 0x1EEB0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0641 }, { 0x1EEB1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0635 }, { 0x1EEB2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0642 }, { 0x1EEB3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0631 }, { 0x1EEB4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0634 }, { 0x1EEB5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062A }, { 0x1EEB6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062B }, { 0x1EEB7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x062E }, { 0x1EEB8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0630 }, { 0x1EEB9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0636 }, { 0x1EEBA, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0638 }, { 0x1EEBB, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x063A }, { 0x1F100, 0, 2 | DECOMP_COMPAT, 4874 }, { 0x1F101, 0, 2 | DECOMP_COMPAT, 4876 }, { 0x1F102, 0, 2 | DECOMP_COMPAT, 4878 }, { 0x1F103, 0, 2 | DECOMP_COMPAT, 4880 }, { 0x1F104, 0, 2 | DECOMP_COMPAT, 4882 }, { 0x1F105, 0, 2 | DECOMP_COMPAT, 4884 }, { 0x1F106, 0, 2 | DECOMP_COMPAT, 4886 }, { 0x1F107, 0, 2 | DECOMP_COMPAT, 4888 }, { 0x1F108, 0, 2 | DECOMP_COMPAT, 4890 }, { 0x1F109, 0, 2 | DECOMP_COMPAT, 4892 }, { 0x1F10A, 0, 2 | DECOMP_COMPAT, 4894 }, { 0x1F110, 0, 3 | DECOMP_COMPAT, 4896 }, { 0x1F111, 0, 3 | DECOMP_COMPAT, 4899 }, { 0x1F112, 0, 3 | DECOMP_COMPAT, 4902 }, { 0x1F113, 0, 3 | DECOMP_COMPAT, 4905 }, { 0x1F114, 0, 3 | DECOMP_COMPAT, 4908 }, { 0x1F115, 0, 3 | DECOMP_COMPAT, 4911 }, { 0x1F116, 0, 3 | DECOMP_COMPAT, 4914 }, { 0x1F117, 0, 3 | DECOMP_COMPAT, 4917 }, { 0x1F118, 0, 3 | DECOMP_COMPAT, 4920 }, { 0x1F119, 0, 3 | DECOMP_COMPAT, 4923 }, { 0x1F11A, 0, 3 | DECOMP_COMPAT, 4926 }, { 0x1F11B, 0, 3 | DECOMP_COMPAT, 4929 }, { 0x1F11C, 0, 3 | DECOMP_COMPAT, 4932 }, { 0x1F11D, 0, 3 | DECOMP_COMPAT, 4935 }, { 0x1F11E, 0, 3 | DECOMP_COMPAT, 4938 }, { 0x1F11F, 0, 3 | DECOMP_COMPAT, 4941 }, { 0x1F120, 0, 3 | DECOMP_COMPAT, 4944 }, { 0x1F121, 0, 3 | DECOMP_COMPAT, 4947 }, { 0x1F122, 0, 3 | DECOMP_COMPAT, 4950 }, { 0x1F123, 0, 3 | DECOMP_COMPAT, 4953 }, { 0x1F124, 0, 3 | DECOMP_COMPAT, 4956 }, { 0x1F125, 0, 3 | DECOMP_COMPAT, 4959 }, { 0x1F126, 0, 3 | DECOMP_COMPAT, 4962 }, { 0x1F127, 0, 3 | DECOMP_COMPAT, 4965 }, { 0x1F128, 0, 3 | DECOMP_COMPAT, 4968 }, { 0x1F129, 0, 3 | DECOMP_COMPAT, 4971 }, { 0x1F12A, 0, 3 | DECOMP_COMPAT, 4974 }, { 0x1F12B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1F12C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1F12D, 0, 2 | DECOMP_COMPAT, 4977 }, { 0x1F12E, 0, 2 | DECOMP_COMPAT, 4979 }, { 0x1F130, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0041 }, { 0x1F131, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0042 }, { 0x1F132, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0043 }, { 0x1F133, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0044 }, { 0x1F134, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0045 }, { 0x1F135, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0046 }, { 0x1F136, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0047 }, { 0x1F137, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0048 }, { 0x1F138, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0049 }, { 0x1F139, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004A }, { 0x1F13A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004B }, { 0x1F13B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004C }, { 0x1F13C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004D }, { 0x1F13D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004E }, { 0x1F13E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x004F }, { 0x1F13F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0050 }, { 0x1F140, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0051 }, { 0x1F141, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0052 }, { 0x1F142, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0053 }, { 0x1F143, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0054 }, { 0x1F144, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0055 }, { 0x1F145, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0056 }, { 0x1F146, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0057 }, { 0x1F147, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0058 }, { 0x1F148, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0059 }, { 0x1F149, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x005A }, { 0x1F14A, 0, 2 | DECOMP_COMPAT, 4981 }, { 0x1F14B, 0, 2 | DECOMP_COMPAT, 4983 }, { 0x1F14C, 0, 2 | DECOMP_COMPAT, 4985 }, { 0x1F14D, 0, 2 | DECOMP_COMPAT, 4987 }, { 0x1F14E, 0, 3 | DECOMP_COMPAT, 4989 }, { 0x1F14F, 0, 2 | DECOMP_COMPAT, 4992 }, { 0x1F16A, 0, 2 | DECOMP_COMPAT, 4994 }, { 0x1F16B, 0, 2 | DECOMP_COMPAT, 4996 }, { 0x1F16C, 0, 2 | DECOMP_COMPAT, 4998 }, { 0x1F190, 0, 2 | DECOMP_COMPAT, 5000 }, { 0x1F200, 0, 2 | DECOMP_COMPAT, 5002 }, { 0x1F201, 0, 2 | DECOMP_COMPAT, 5004 }, { 0x1F202, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30B5 }, { 0x1F210, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x624B }, { 0x1F211, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5B57 }, { 0x1F212, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53CC }, { 0x1F213, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x30C7 }, { 0x1F214, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E8C }, { 0x1F215, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x591A }, { 0x1F216, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x89E3 }, { 0x1F217, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5929 }, { 0x1F218, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4EA4 }, { 0x1F219, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6620 }, { 0x1F21A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7121 }, { 0x1F21B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6599 }, { 0x1F21C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x524D }, { 0x1F21D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F8C }, { 0x1F21E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x518D }, { 0x1F21F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x65B0 }, { 0x1F220, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x521D }, { 0x1F221, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7D42 }, { 0x1F222, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x751F }, { 0x1F223, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8CA9 }, { 0x1F224, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x58F0 }, { 0x1F225, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5439 }, { 0x1F226, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6F14 }, { 0x1F227, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6295 }, { 0x1F228, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6355 }, { 0x1F229, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E00 }, { 0x1F22A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E09 }, { 0x1F22B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x904A }, { 0x1F22C, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5DE6 }, { 0x1F22D, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x4E2D }, { 0x1F22E, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53F3 }, { 0x1F22F, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6307 }, { 0x1F230, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x8D70 }, { 0x1F231, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6253 }, { 0x1F232, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7981 }, { 0x1F233, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7A7A }, { 0x1F234, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5408 }, { 0x1F235, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6E80 }, { 0x1F236, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6709 }, { 0x1F237, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x6708 }, { 0x1F238, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x7533 }, { 0x1F239, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5272 }, { 0x1F23A, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x55B6 }, { 0x1F23B, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x914D }, { 0x1F240, 0, 3 | DECOMP_COMPAT, 5006 }, { 0x1F241, 0, 3 | DECOMP_COMPAT, 5009 }, { 0x1F242, 0, 3 | DECOMP_COMPAT, 5012 }, { 0x1F243, 0, 3 | DECOMP_COMPAT, 5015 }, { 0x1F244, 0, 3 | DECOMP_COMPAT, 5018 }, { 0x1F245, 0, 3 | DECOMP_COMPAT, 5021 }, { 0x1F246, 0, 3 | DECOMP_COMPAT, 5024 }, { 0x1F247, 0, 3 | DECOMP_COMPAT, 5027 }, { 0x1F248, 0, 3 | DECOMP_COMPAT, 5030 }, { 0x1F250, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x5F97 }, { 0x1F251, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x53EF }, { 0x1FBF0, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0030 }, { 0x1FBF1, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0031 }, { 0x1FBF2, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0032 }, { 0x1FBF3, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0033 }, { 0x1FBF4, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0034 }, { 0x1FBF5, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0035 }, { 0x1FBF6, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0036 }, { 0x1FBF7, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0037 }, { 0x1FBF8, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0038 }, { 0x1FBF9, 0, 1 | DECOMP_COMPAT | DECOMP_INLINE, 0x0039 }, { 0x2F800, 0, 1 | DECOMP_INLINE, 0x4E3D }, { 0x2F801, 0, 1 | DECOMP_INLINE, 0x4E38 }, { 0x2F802, 0, 1 | DECOMP_INLINE, 0x4E41 }, { 0x2F803, 0, 1, 5033 }, { 0x2F804, 0, 1 | DECOMP_INLINE, 0x4F60 }, { 0x2F805, 0, 1 | DECOMP_INLINE, 0x4FAE }, { 0x2F806, 0, 1 | DECOMP_INLINE, 0x4FBB }, { 0x2F807, 0, 1 | DECOMP_INLINE, 0x5002 }, { 0x2F808, 0, 1 | DECOMP_INLINE, 0x507A }, { 0x2F809, 0, 1 | DECOMP_INLINE, 0x5099 }, { 0x2F80A, 0, 1 | DECOMP_INLINE, 0x50E7 }, { 0x2F80B, 0, 1 | DECOMP_INLINE, 0x50CF }, { 0x2F80C, 0, 1 | DECOMP_INLINE, 0x349E }, { 0x2F80D, 0, 1, 5034 }, { 0x2F80E, 0, 1 | DECOMP_INLINE, 0x514D }, { 0x2F80F, 0, 1 | DECOMP_INLINE, 0x5154 }, { 0x2F810, 0, 1 | DECOMP_INLINE, 0x5164 }, { 0x2F811, 0, 1 | DECOMP_INLINE, 0x5177 }, { 0x2F812, 0, 1, 5035 }, { 0x2F813, 0, 1 | DECOMP_INLINE, 0x34B9 }, { 0x2F814, 0, 1 | DECOMP_INLINE, 0x5167 }, { 0x2F815, 0, 1 | DECOMP_INLINE, 0x518D }, { 0x2F816, 0, 1, 5036 }, { 0x2F817, 0, 1 | DECOMP_INLINE, 0x5197 }, { 0x2F818, 0, 1 | DECOMP_INLINE, 0x51A4 }, { 0x2F819, 0, 1 | DECOMP_INLINE, 0x4ECC }, { 0x2F81A, 0, 1 | DECOMP_INLINE, 0x51AC }, { 0x2F81B, 0, 1 | DECOMP_INLINE, 0x51B5 }, { 0x2F81C, 0, 1, 5037 }, { 0x2F81D, 0, 1 | DECOMP_INLINE, 0x51F5 }, { 0x2F81E, 0, 1 | DECOMP_INLINE, 0x5203 }, { 0x2F81F, 0, 1 | DECOMP_INLINE, 0x34DF }, { 0x2F820, 0, 1 | DECOMP_INLINE, 0x523B }, { 0x2F821, 0, 1 | DECOMP_INLINE, 0x5246 }, { 0x2F822, 0, 1 | DECOMP_INLINE, 0x5272 }, { 0x2F823, 0, 1 | DECOMP_INLINE, 0x5277 }, { 0x2F824, 0, 1 | DECOMP_INLINE, 0x3515 }, { 0x2F825, 0, 1 | DECOMP_INLINE, 0x52C7 }, { 0x2F826, 0, 1 | DECOMP_INLINE, 0x52C9 }, { 0x2F827, 0, 1 | DECOMP_INLINE, 0x52E4 }, { 0x2F828, 0, 1 | DECOMP_INLINE, 0x52FA }, { 0x2F829, 0, 1 | DECOMP_INLINE, 0x5305 }, { 0x2F82A, 0, 1 | DECOMP_INLINE, 0x5306 }, { 0x2F82B, 0, 1 | DECOMP_INLINE, 0x5317 }, { 0x2F82C, 0, 1 | DECOMP_INLINE, 0x5349 }, { 0x2F82D, 0, 1 | DECOMP_INLINE, 0x5351 }, { 0x2F82E, 0, 1 | DECOMP_INLINE, 0x535A }, { 0x2F82F, 0, 1 | DECOMP_INLINE, 0x5373 }, { 0x2F830, 0, 1 | DECOMP_INLINE, 0x537D }, { 0x2F831, 0, 1 | DECOMP_INLINE, 0x537F }, { 0x2F832, 0, 1 | DECOMP_INLINE, 0x537F }, { 0x2F833, 0, 1 | DECOMP_INLINE, 0x537F }, { 0x2F834, 0, 1, 5038 }, { 0x2F835, 0, 1 | DECOMP_INLINE, 0x7070 }, { 0x2F836, 0, 1 | DECOMP_INLINE, 0x53CA }, { 0x2F837, 0, 1 | DECOMP_INLINE, 0x53DF }, { 0x2F838, 0, 1, 5039 }, { 0x2F839, 0, 1 | DECOMP_INLINE, 0x53EB }, { 0x2F83A, 0, 1 | DECOMP_INLINE, 0x53F1 }, { 0x2F83B, 0, 1 | DECOMP_INLINE, 0x5406 }, { 0x2F83C, 0, 1 | DECOMP_INLINE, 0x549E }, { 0x2F83D, 0, 1 | DECOMP_INLINE, 0x5438 }, { 0x2F83E, 0, 1 | DECOMP_INLINE, 0x5448 }, { 0x2F83F, 0, 1 | DECOMP_INLINE, 0x5468 }, { 0x2F840, 0, 1 | DECOMP_INLINE, 0x54A2 }, { 0x2F841, 0, 1 | DECOMP_INLINE, 0x54F6 }, { 0x2F842, 0, 1 | DECOMP_INLINE, 0x5510 }, { 0x2F843, 0, 1 | DECOMP_INLINE, 0x5553 }, { 0x2F844, 0, 1 | DECOMP_INLINE, 0x5563 }, { 0x2F845, 0, 1 | DECOMP_INLINE, 0x5584 }, { 0x2F846, 0, 1 | DECOMP_INLINE, 0x5584 }, { 0x2F847, 0, 1 | DECOMP_INLINE, 0x5599 }, { 0x2F848, 0, 1 | DECOMP_INLINE, 0x55AB }, { 0x2F849, 0, 1 | DECOMP_INLINE, 0x55B3 }, { 0x2F84A, 0, 1 | DECOMP_INLINE, 0x55C2 }, { 0x2F84B, 0, 1 | DECOMP_INLINE, 0x5716 }, { 0x2F84C, 0, 1 | DECOMP_INLINE, 0x5606 }, { 0x2F84D, 0, 1 | DECOMP_INLINE, 0x5717 }, { 0x2F84E, 0, 1 | DECOMP_INLINE, 0x5651 }, { 0x2F84F, 0, 1 | DECOMP_INLINE, 0x5674 }, { 0x2F850, 0, 1 | DECOMP_INLINE, 0x5207 }, { 0x2F851, 0, 1 | DECOMP_INLINE, 0x58EE }, { 0x2F852, 0, 1 | DECOMP_INLINE, 0x57CE }, { 0x2F853, 0, 1 | DECOMP_INLINE, 0x57F4 }, { 0x2F854, 0, 1 | DECOMP_INLINE, 0x580D }, { 0x2F855, 0, 1 | DECOMP_INLINE, 0x578B }, { 0x2F856, 0, 1 | DECOMP_INLINE, 0x5832 }, { 0x2F857, 0, 1 | DECOMP_INLINE, 0x5831 }, { 0x2F858, 0, 1 | DECOMP_INLINE, 0x58AC }, { 0x2F859, 0, 1, 5040 }, { 0x2F85A, 0, 1 | DECOMP_INLINE, 0x58F2 }, { 0x2F85B, 0, 1 | DECOMP_INLINE, 0x58F7 }, { 0x2F85C, 0, 1 | DECOMP_INLINE, 0x5906 }, { 0x2F85D, 0, 1 | DECOMP_INLINE, 0x591A }, { 0x2F85E, 0, 1 | DECOMP_INLINE, 0x5922 }, { 0x2F85F, 0, 1 | DECOMP_INLINE, 0x5962 }, { 0x2F860, 0, 1, 5041 }, { 0x2F861, 0, 1, 5042 }, { 0x2F862, 0, 1 | DECOMP_INLINE, 0x59EC }, { 0x2F863, 0, 1 | DECOMP_INLINE, 0x5A1B }, { 0x2F864, 0, 1 | DECOMP_INLINE, 0x5A27 }, { 0x2F865, 0, 1 | DECOMP_INLINE, 0x59D8 }, { 0x2F866, 0, 1 | DECOMP_INLINE, 0x5A66 }, { 0x2F867, 0, 1 | DECOMP_INLINE, 0x36EE }, { 0x2F868, 0, 1 | DECOMP_INLINE, 0x36FC }, { 0x2F869, 0, 1 | DECOMP_INLINE, 0x5B08 }, { 0x2F86A, 0, 1 | DECOMP_INLINE, 0x5B3E }, { 0x2F86B, 0, 1 | DECOMP_INLINE, 0x5B3E }, { 0x2F86C, 0, 1, 5043 }, { 0x2F86D, 0, 1 | DECOMP_INLINE, 0x5BC3 }, { 0x2F86E, 0, 1 | DECOMP_INLINE, 0x5BD8 }, { 0x2F86F, 0, 1 | DECOMP_INLINE, 0x5BE7 }, { 0x2F870, 0, 1 | DECOMP_INLINE, 0x5BF3 }, { 0x2F871, 0, 1, 5044 }, { 0x2F872, 0, 1 | DECOMP_INLINE, 0x5BFF }, { 0x2F873, 0, 1 | DECOMP_INLINE, 0x5C06 }, { 0x2F874, 0, 1 | DECOMP_INLINE, 0x5F53 }, { 0x2F875, 0, 1 | DECOMP_INLINE, 0x5C22 }, { 0x2F876, 0, 1 | DECOMP_INLINE, 0x3781 }, { 0x2F877, 0, 1 | DECOMP_INLINE, 0x5C60 }, { 0x2F878, 0, 1 | DECOMP_INLINE, 0x5C6E }, { 0x2F879, 0, 1 | DECOMP_INLINE, 0x5CC0 }, { 0x2F87A, 0, 1 | DECOMP_INLINE, 0x5C8D }, { 0x2F87B, 0, 1, 5045 }, { 0x2F87C, 0, 1 | DECOMP_INLINE, 0x5D43 }, { 0x2F87D, 0, 1, 5046 }, { 0x2F87E, 0, 1 | DECOMP_INLINE, 0x5D6E }, { 0x2F87F, 0, 1 | DECOMP_INLINE, 0x5D6B }, { 0x2F880, 0, 1 | DECOMP_INLINE, 0x5D7C }, { 0x2F881, 0, 1 | DECOMP_INLINE, 0x5DE1 }, { 0x2F882, 0, 1 | DECOMP_INLINE, 0x5DE2 }, { 0x2F883, 0, 1 | DECOMP_INLINE, 0x382F }, { 0x2F884, 0, 1 | DECOMP_INLINE, 0x5DFD }, { 0x2F885, 0, 1 | DECOMP_INLINE, 0x5E28 }, { 0x2F886, 0, 1 | DECOMP_INLINE, 0x5E3D }, { 0x2F887, 0, 1 | DECOMP_INLINE, 0x5E69 }, { 0x2F888, 0, 1 | DECOMP_INLINE, 0x3862 }, { 0x2F889, 0, 1, 5047 }, { 0x2F88A, 0, 1 | DECOMP_INLINE, 0x387C }, { 0x2F88B, 0, 1 | DECOMP_INLINE, 0x5EB0 }, { 0x2F88C, 0, 1 | DECOMP_INLINE, 0x5EB3 }, { 0x2F88D, 0, 1 | DECOMP_INLINE, 0x5EB6 }, { 0x2F88E, 0, 1 | DECOMP_INLINE, 0x5ECA }, { 0x2F88F, 0, 1, 5048 }, { 0x2F890, 0, 1 | DECOMP_INLINE, 0x5EFE }, { 0x2F891, 0, 1, 5049 }, { 0x2F892, 0, 1, 5050 }, { 0x2F893, 0, 1 | DECOMP_INLINE, 0x8201 }, { 0x2F894, 0, 1 | DECOMP_INLINE, 0x5F22 }, { 0x2F895, 0, 1 | DECOMP_INLINE, 0x5F22 }, { 0x2F896, 0, 1 | DECOMP_INLINE, 0x38C7 }, { 0x2F897, 0, 1, 5051 }, { 0x2F898, 0, 1, 5052 }, { 0x2F899, 0, 1 | DECOMP_INLINE, 0x5F62 }, { 0x2F89A, 0, 1 | DECOMP_INLINE, 0x5F6B }, { 0x2F89B, 0, 1 | DECOMP_INLINE, 0x38E3 }, { 0x2F89C, 0, 1 | DECOMP_INLINE, 0x5F9A }, { 0x2F89D, 0, 1 | DECOMP_INLINE, 0x5FCD }, { 0x2F89E, 0, 1 | DECOMP_INLINE, 0x5FD7 }, { 0x2F89F, 0, 1 | DECOMP_INLINE, 0x5FF9 }, { 0x2F8A0, 0, 1 | DECOMP_INLINE, 0x6081 }, { 0x2F8A1, 0, 1 | DECOMP_INLINE, 0x393A }, { 0x2F8A2, 0, 1 | DECOMP_INLINE, 0x391C }, { 0x2F8A3, 0, 1 | DECOMP_INLINE, 0x6094 }, { 0x2F8A4, 0, 1, 5053 }, { 0x2F8A5, 0, 1 | DECOMP_INLINE, 0x60C7 }, { 0x2F8A6, 0, 1 | DECOMP_INLINE, 0x6148 }, { 0x2F8A7, 0, 1 | DECOMP_INLINE, 0x614C }, { 0x2F8A8, 0, 1 | DECOMP_INLINE, 0x614E }, { 0x2F8A9, 0, 1 | DECOMP_INLINE, 0x614C }, { 0x2F8AA, 0, 1 | DECOMP_INLINE, 0x617A }, { 0x2F8AB, 0, 1 | DECOMP_INLINE, 0x618E }, { 0x2F8AC, 0, 1 | DECOMP_INLINE, 0x61B2 }, { 0x2F8AD, 0, 1 | DECOMP_INLINE, 0x61A4 }, { 0x2F8AE, 0, 1 | DECOMP_INLINE, 0x61AF }, { 0x2F8AF, 0, 1 | DECOMP_INLINE, 0x61DE }, { 0x2F8B0, 0, 1 | DECOMP_INLINE, 0x61F2 }, { 0x2F8B1, 0, 1 | DECOMP_INLINE, 0x61F6 }, { 0x2F8B2, 0, 1 | DECOMP_INLINE, 0x6210 }, { 0x2F8B3, 0, 1 | DECOMP_INLINE, 0x621B }, { 0x2F8B4, 0, 1 | DECOMP_INLINE, 0x625D }, { 0x2F8B5, 0, 1 | DECOMP_INLINE, 0x62B1 }, { 0x2F8B6, 0, 1 | DECOMP_INLINE, 0x62D4 }, { 0x2F8B7, 0, 1 | DECOMP_INLINE, 0x6350 }, { 0x2F8B8, 0, 1, 5054 }, { 0x2F8B9, 0, 1 | DECOMP_INLINE, 0x633D }, { 0x2F8BA, 0, 1 | DECOMP_INLINE, 0x62FC }, { 0x2F8BB, 0, 1 | DECOMP_INLINE, 0x6368 }, { 0x2F8BC, 0, 1 | DECOMP_INLINE, 0x6383 }, { 0x2F8BD, 0, 1 | DECOMP_INLINE, 0x63E4 }, { 0x2F8BE, 0, 1, 5055 }, { 0x2F8BF, 0, 1 | DECOMP_INLINE, 0x6422 }, { 0x2F8C0, 0, 1 | DECOMP_INLINE, 0x63C5 }, { 0x2F8C1, 0, 1 | DECOMP_INLINE, 0x63A9 }, { 0x2F8C2, 0, 1 | DECOMP_INLINE, 0x3A2E }, { 0x2F8C3, 0, 1 | DECOMP_INLINE, 0x6469 }, { 0x2F8C4, 0, 1 | DECOMP_INLINE, 0x647E }, { 0x2F8C5, 0, 1 | DECOMP_INLINE, 0x649D }, { 0x2F8C6, 0, 1 | DECOMP_INLINE, 0x6477 }, { 0x2F8C7, 0, 1 | DECOMP_INLINE, 0x3A6C }, { 0x2F8C8, 0, 1 | DECOMP_INLINE, 0x654F }, { 0x2F8C9, 0, 1 | DECOMP_INLINE, 0x656C }, { 0x2F8CA, 0, 1, 5056 }, { 0x2F8CB, 0, 1 | DECOMP_INLINE, 0x65E3 }, { 0x2F8CC, 0, 1 | DECOMP_INLINE, 0x66F8 }, { 0x2F8CD, 0, 1 | DECOMP_INLINE, 0x6649 }, { 0x2F8CE, 0, 1 | DECOMP_INLINE, 0x3B19 }, { 0x2F8CF, 0, 1 | DECOMP_INLINE, 0x6691 }, { 0x2F8D0, 0, 1 | DECOMP_INLINE, 0x3B08 }, { 0x2F8D1, 0, 1 | DECOMP_INLINE, 0x3AE4 }, { 0x2F8D2, 0, 1 | DECOMP_INLINE, 0x5192 }, { 0x2F8D3, 0, 1 | DECOMP_INLINE, 0x5195 }, { 0x2F8D4, 0, 1 | DECOMP_INLINE, 0x6700 }, { 0x2F8D5, 0, 1 | DECOMP_INLINE, 0x669C }, { 0x2F8D6, 0, 1 | DECOMP_INLINE, 0x80AD }, { 0x2F8D7, 0, 1 | DECOMP_INLINE, 0x43D9 }, { 0x2F8D8, 0, 1 | DECOMP_INLINE, 0x6717 }, { 0x2F8D9, 0, 1 | DECOMP_INLINE, 0x671B }, { 0x2F8DA, 0, 1 | DECOMP_INLINE, 0x6721 }, { 0x2F8DB, 0, 1 | DECOMP_INLINE, 0x675E }, { 0x2F8DC, 0, 1 | DECOMP_INLINE, 0x6753 }, { 0x2F8DD, 0, 1, 5057 }, { 0x2F8DE, 0, 1 | DECOMP_INLINE, 0x3B49 }, { 0x2F8DF, 0, 1 | DECOMP_INLINE, 0x67FA }, { 0x2F8E0, 0, 1 | DECOMP_INLINE, 0x6785 }, { 0x2F8E1, 0, 1 | DECOMP_INLINE, 0x6852 }, { 0x2F8E2, 0, 1 | DECOMP_INLINE, 0x6885 }, { 0x2F8E3, 0, 1, 5058 }, { 0x2F8E4, 0, 1 | DECOMP_INLINE, 0x688E }, { 0x2F8E5, 0, 1 | DECOMP_INLINE, 0x681F }, { 0x2F8E6, 0, 1 | DECOMP_INLINE, 0x6914 }, { 0x2F8E7, 0, 1 | DECOMP_INLINE, 0x3B9D }, { 0x2F8E8, 0, 1 | DECOMP_INLINE, 0x6942 }, { 0x2F8E9, 0, 1 | DECOMP_INLINE, 0x69A3 }, { 0x2F8EA, 0, 1 | DECOMP_INLINE, 0x69EA }, { 0x2F8EB, 0, 1 | DECOMP_INLINE, 0x6AA8 }, { 0x2F8EC, 0, 1, 5059 }, { 0x2F8ED, 0, 1 | DECOMP_INLINE, 0x6ADB }, { 0x2F8EE, 0, 1 | DECOMP_INLINE, 0x3C18 }, { 0x2F8EF, 0, 1 | DECOMP_INLINE, 0x6B21 }, { 0x2F8F0, 0, 1, 5060 }, { 0x2F8F1, 0, 1 | DECOMP_INLINE, 0x6B54 }, { 0x2F8F2, 0, 1 | DECOMP_INLINE, 0x3C4E }, { 0x2F8F3, 0, 1 | DECOMP_INLINE, 0x6B72 }, { 0x2F8F4, 0, 1 | DECOMP_INLINE, 0x6B9F }, { 0x2F8F5, 0, 1 | DECOMP_INLINE, 0x6BBA }, { 0x2F8F6, 0, 1 | DECOMP_INLINE, 0x6BBB }, { 0x2F8F7, 0, 1, 5061 }, { 0x2F8F8, 0, 1, 5062 }, { 0x2F8F9, 0, 1, 5063 }, { 0x2F8FA, 0, 1 | DECOMP_INLINE, 0x6C4E }, { 0x2F8FB, 0, 1, 5064 }, { 0x2F8FC, 0, 1 | DECOMP_INLINE, 0x6CBF }, { 0x2F8FD, 0, 1 | DECOMP_INLINE, 0x6CCD }, { 0x2F8FE, 0, 1 | DECOMP_INLINE, 0x6C67 }, { 0x2F8FF, 0, 1 | DECOMP_INLINE, 0x6D16 }, { 0x2F900, 0, 1 | DECOMP_INLINE, 0x6D3E }, { 0x2F901, 0, 1 | DECOMP_INLINE, 0x6D77 }, { 0x2F902, 0, 1 | DECOMP_INLINE, 0x6D41 }, { 0x2F903, 0, 1 | DECOMP_INLINE, 0x6D69 }, { 0x2F904, 0, 1 | DECOMP_INLINE, 0x6D78 }, { 0x2F905, 0, 1 | DECOMP_INLINE, 0x6D85 }, { 0x2F906, 0, 1, 5065 }, { 0x2F907, 0, 1 | DECOMP_INLINE, 0x6D34 }, { 0x2F908, 0, 1 | DECOMP_INLINE, 0x6E2F }, { 0x2F909, 0, 1 | DECOMP_INLINE, 0x6E6E }, { 0x2F90A, 0, 1 | DECOMP_INLINE, 0x3D33 }, { 0x2F90B, 0, 1 | DECOMP_INLINE, 0x6ECB }, { 0x2F90C, 0, 1 | DECOMP_INLINE, 0x6EC7 }, { 0x2F90D, 0, 1, 5066 }, { 0x2F90E, 0, 1 | DECOMP_INLINE, 0x6DF9 }, { 0x2F90F, 0, 1 | DECOMP_INLINE, 0x6F6E }, { 0x2F910, 0, 1, 5067 }, { 0x2F911, 0, 1, 5068 }, { 0x2F912, 0, 1 | DECOMP_INLINE, 0x6FC6 }, { 0x2F913, 0, 1 | DECOMP_INLINE, 0x7039 }, { 0x2F914, 0, 1 | DECOMP_INLINE, 0x701E }, { 0x2F915, 0, 1 | DECOMP_INLINE, 0x701B }, { 0x2F916, 0, 1 | DECOMP_INLINE, 0x3D96 }, { 0x2F917, 0, 1 | DECOMP_INLINE, 0x704A }, { 0x2F918, 0, 1 | DECOMP_INLINE, 0x707D }, { 0x2F919, 0, 1 | DECOMP_INLINE, 0x7077 }, { 0x2F91A, 0, 1 | DECOMP_INLINE, 0x70AD }, { 0x2F91B, 0, 1, 5069 }, { 0x2F91C, 0, 1 | DECOMP_INLINE, 0x7145 }, { 0x2F91D, 0, 1, 5070 }, { 0x2F91E, 0, 1 | DECOMP_INLINE, 0x719C }, { 0x2F91F, 0, 1, 5071 }, { 0x2F920, 0, 1 | DECOMP_INLINE, 0x7228 }, { 0x2F921, 0, 1 | DECOMP_INLINE, 0x7235 }, { 0x2F922, 0, 1 | DECOMP_INLINE, 0x7250 }, { 0x2F923, 0, 1, 5072 }, { 0x2F924, 0, 1 | DECOMP_INLINE, 0x7280 }, { 0x2F925, 0, 1 | DECOMP_INLINE, 0x7295 }, { 0x2F926, 0, 1, 5073 }, { 0x2F927, 0, 1, 5074 }, { 0x2F928, 0, 1 | DECOMP_INLINE, 0x737A }, { 0x2F929, 0, 1 | DECOMP_INLINE, 0x738B }, { 0x2F92A, 0, 1 | DECOMP_INLINE, 0x3EAC }, { 0x2F92B, 0, 1 | DECOMP_INLINE, 0x73A5 }, { 0x2F92C, 0, 1 | DECOMP_INLINE, 0x3EB8 }, { 0x2F92D, 0, 1 | DECOMP_INLINE, 0x3EB8 }, { 0x2F92E, 0, 1 | DECOMP_INLINE, 0x7447 }, { 0x2F92F, 0, 1 | DECOMP_INLINE, 0x745C }, { 0x2F930, 0, 1 | DECOMP_INLINE, 0x7471 }, { 0x2F931, 0, 1 | DECOMP_INLINE, 0x7485 }, { 0x2F932, 0, 1 | DECOMP_INLINE, 0x74CA }, { 0x2F933, 0, 1 | DECOMP_INLINE, 0x3F1B }, { 0x2F934, 0, 1 | DECOMP_INLINE, 0x7524 }, { 0x2F935, 0, 1, 5075 }, { 0x2F936, 0, 1 | DECOMP_INLINE, 0x753E }, { 0x2F937, 0, 1, 5076 }, { 0x2F938, 0, 1 | DECOMP_INLINE, 0x7570 }, { 0x2F939, 0, 1, 5077 }, { 0x2F93A, 0, 1 | DECOMP_INLINE, 0x7610 }, { 0x2F93B, 0, 1, 5078 }, { 0x2F93C, 0, 1, 5079 }, { 0x2F93D, 0, 1, 5080 }, { 0x2F93E, 0, 1 | DECOMP_INLINE, 0x3FFC }, { 0x2F93F, 0, 1 | DECOMP_INLINE, 0x4008 }, { 0x2F940, 0, 1 | DECOMP_INLINE, 0x76F4 }, { 0x2F941, 0, 1, 5081 }, { 0x2F942, 0, 1, 5082 }, { 0x2F943, 0, 1, 5083 }, { 0x2F944, 0, 1, 5084 }, { 0x2F945, 0, 1 | DECOMP_INLINE, 0x771E }, { 0x2F946, 0, 1 | DECOMP_INLINE, 0x771F }, { 0x2F947, 0, 1 | DECOMP_INLINE, 0x771F }, { 0x2F948, 0, 1 | DECOMP_INLINE, 0x774A }, { 0x2F949, 0, 1 | DECOMP_INLINE, 0x4039 }, { 0x2F94A, 0, 1 | DECOMP_INLINE, 0x778B }, { 0x2F94B, 0, 1 | DECOMP_INLINE, 0x4046 }, { 0x2F94C, 0, 1 | DECOMP_INLINE, 0x4096 }, { 0x2F94D, 0, 1, 5085 }, { 0x2F94E, 0, 1 | DECOMP_INLINE, 0x784E }, { 0x2F94F, 0, 1 | DECOMP_INLINE, 0x788C }, { 0x2F950, 0, 1 | DECOMP_INLINE, 0x78CC }, { 0x2F951, 0, 1 | DECOMP_INLINE, 0x40E3 }, { 0x2F952, 0, 1, 5086 }, { 0x2F953, 0, 1 | DECOMP_INLINE, 0x7956 }, { 0x2F954, 0, 1, 5087 }, { 0x2F955, 0, 1, 5088 }, { 0x2F956, 0, 1 | DECOMP_INLINE, 0x798F }, { 0x2F957, 0, 1 | DECOMP_INLINE, 0x79EB }, { 0x2F958, 0, 1 | DECOMP_INLINE, 0x412F }, { 0x2F959, 0, 1 | DECOMP_INLINE, 0x7A40 }, { 0x2F95A, 0, 1 | DECOMP_INLINE, 0x7A4A }, { 0x2F95B, 0, 1 | DECOMP_INLINE, 0x7A4F }, { 0x2F95C, 0, 1, 5089 }, { 0x2F95D, 0, 1, 5090 }, { 0x2F95E, 0, 1, 5091 }, { 0x2F95F, 0, 1 | DECOMP_INLINE, 0x7AEE }, { 0x2F960, 0, 1 | DECOMP_INLINE, 0x4202 }, { 0x2F961, 0, 1, 5092 }, { 0x2F962, 0, 1 | DECOMP_INLINE, 0x7BC6 }, { 0x2F963, 0, 1 | DECOMP_INLINE, 0x7BC9 }, { 0x2F964, 0, 1 | DECOMP_INLINE, 0x4227 }, { 0x2F965, 0, 1, 5093 }, { 0x2F966, 0, 1 | DECOMP_INLINE, 0x7CD2 }, { 0x2F967, 0, 1 | DECOMP_INLINE, 0x42A0 }, { 0x2F968, 0, 1 | DECOMP_INLINE, 0x7CE8 }, { 0x2F969, 0, 1 | DECOMP_INLINE, 0x7CE3 }, { 0x2F96A, 0, 1 | DECOMP_INLINE, 0x7D00 }, { 0x2F96B, 0, 1, 5094 }, { 0x2F96C, 0, 1 | DECOMP_INLINE, 0x7D63 }, { 0x2F96D, 0, 1 | DECOMP_INLINE, 0x4301 }, { 0x2F96E, 0, 1 | DECOMP_INLINE, 0x7DC7 }, { 0x2F96F, 0, 1 | DECOMP_INLINE, 0x7E02 }, { 0x2F970, 0, 1 | DECOMP_INLINE, 0x7E45 }, { 0x2F971, 0, 1 | DECOMP_INLINE, 0x4334 }, { 0x2F972, 0, 1, 5095 }, { 0x2F973, 0, 1, 5096 }, { 0x2F974, 0, 1 | DECOMP_INLINE, 0x4359 }, { 0x2F975, 0, 1, 5097 }, { 0x2F976, 0, 1 | DECOMP_INLINE, 0x7F7A }, { 0x2F977, 0, 1, 5098 }, { 0x2F978, 0, 1 | DECOMP_INLINE, 0x7F95 }, { 0x2F979, 0, 1 | DECOMP_INLINE, 0x7FFA }, { 0x2F97A, 0, 1 | DECOMP_INLINE, 0x8005 }, { 0x2F97B, 0, 1, 5099 }, { 0x2F97C, 0, 1, 5100 }, { 0x2F97D, 0, 1 | DECOMP_INLINE, 0x8060 }, { 0x2F97E, 0, 1, 5101 }, { 0x2F97F, 0, 1 | DECOMP_INLINE, 0x8070 }, { 0x2F980, 0, 1, 5102 }, { 0x2F981, 0, 1 | DECOMP_INLINE, 0x43D5 }, { 0x2F982, 0, 1 | DECOMP_INLINE, 0x80B2 }, { 0x2F983, 0, 1 | DECOMP_INLINE, 0x8103 }, { 0x2F984, 0, 1 | DECOMP_INLINE, 0x440B }, { 0x2F985, 0, 1 | DECOMP_INLINE, 0x813E }, { 0x2F986, 0, 1 | DECOMP_INLINE, 0x5AB5 }, { 0x2F987, 0, 1, 5103 }, { 0x2F988, 0, 1, 5104 }, { 0x2F989, 0, 1, 5105 }, { 0x2F98A, 0, 1, 5106 }, { 0x2F98B, 0, 1 | DECOMP_INLINE, 0x8201 }, { 0x2F98C, 0, 1 | DECOMP_INLINE, 0x8204 }, { 0x2F98D, 0, 1 | DECOMP_INLINE, 0x8F9E }, { 0x2F98E, 0, 1 | DECOMP_INLINE, 0x446B }, { 0x2F98F, 0, 1 | DECOMP_INLINE, 0x8291 }, { 0x2F990, 0, 1 | DECOMP_INLINE, 0x828B }, { 0x2F991, 0, 1 | DECOMP_INLINE, 0x829D }, { 0x2F992, 0, 1 | DECOMP_INLINE, 0x52B3 }, { 0x2F993, 0, 1 | DECOMP_INLINE, 0x82B1 }, { 0x2F994, 0, 1 | DECOMP_INLINE, 0x82B3 }, { 0x2F995, 0, 1 | DECOMP_INLINE, 0x82BD }, { 0x2F996, 0, 1 | DECOMP_INLINE, 0x82E6 }, { 0x2F997, 0, 1, 5107 }, { 0x2F998, 0, 1 | DECOMP_INLINE, 0x82E5 }, { 0x2F999, 0, 1 | DECOMP_INLINE, 0x831D }, { 0x2F99A, 0, 1 | DECOMP_INLINE, 0x8363 }, { 0x2F99B, 0, 1 | DECOMP_INLINE, 0x83AD }, { 0x2F99C, 0, 1 | DECOMP_INLINE, 0x8323 }, { 0x2F99D, 0, 1 | DECOMP_INLINE, 0x83BD }, { 0x2F99E, 0, 1 | DECOMP_INLINE, 0x83E7 }, { 0x2F99F, 0, 1 | DECOMP_INLINE, 0x8457 }, { 0x2F9A0, 0, 1 | DECOMP_INLINE, 0x8353 }, { 0x2F9A1, 0, 1 | DECOMP_INLINE, 0x83CA }, { 0x2F9A2, 0, 1 | DECOMP_INLINE, 0x83CC }, { 0x2F9A3, 0, 1 | DECOMP_INLINE, 0x83DC }, { 0x2F9A4, 0, 1, 5108 }, { 0x2F9A5, 0, 1, 5109 }, { 0x2F9A6, 0, 1, 5110 }, { 0x2F9A7, 0, 1 | DECOMP_INLINE, 0x452B }, { 0x2F9A8, 0, 1 | DECOMP_INLINE, 0x84F1 }, { 0x2F9A9, 0, 1 | DECOMP_INLINE, 0x84F3 }, { 0x2F9AA, 0, 1 | DECOMP_INLINE, 0x8516 }, { 0x2F9AB, 0, 1, 5111 }, { 0x2F9AC, 0, 1 | DECOMP_INLINE, 0x8564 }, { 0x2F9AD, 0, 1, 5112 }, { 0x2F9AE, 0, 1 | DECOMP_INLINE, 0x455D }, { 0x2F9AF, 0, 1 | DECOMP_INLINE, 0x4561 }, { 0x2F9B0, 0, 1, 5113 }, { 0x2F9B1, 0, 1, 5114 }, { 0x2F9B2, 0, 1 | DECOMP_INLINE, 0x456B }, { 0x2F9B3, 0, 1 | DECOMP_INLINE, 0x8650 }, { 0x2F9B4, 0, 1 | DECOMP_INLINE, 0x865C }, { 0x2F9B5, 0, 1 | DECOMP_INLINE, 0x8667 }, { 0x2F9B6, 0, 1 | DECOMP_INLINE, 0x8669 }, { 0x2F9B7, 0, 1 | DECOMP_INLINE, 0x86A9 }, { 0x2F9B8, 0, 1 | DECOMP_INLINE, 0x8688 }, { 0x2F9B9, 0, 1 | DECOMP_INLINE, 0x870E }, { 0x2F9BA, 0, 1 | DECOMP_INLINE, 0x86E2 }, { 0x2F9BB, 0, 1 | DECOMP_INLINE, 0x8779 }, { 0x2F9BC, 0, 1 | DECOMP_INLINE, 0x8728 }, { 0x2F9BD, 0, 1 | DECOMP_INLINE, 0x876B }, { 0x2F9BE, 0, 1 | DECOMP_INLINE, 0x8786 }, { 0x2F9BF, 0, 1 | DECOMP_INLINE, 0x45D7 }, { 0x2F9C0, 0, 1 | DECOMP_INLINE, 0x87E1 }, { 0x2F9C1, 0, 1 | DECOMP_INLINE, 0x8801 }, { 0x2F9C2, 0, 1 | DECOMP_INLINE, 0x45F9 }, { 0x2F9C3, 0, 1 | DECOMP_INLINE, 0x8860 }, { 0x2F9C4, 0, 1 | DECOMP_INLINE, 0x8863 }, { 0x2F9C5, 0, 1, 5115 }, { 0x2F9C6, 0, 1 | DECOMP_INLINE, 0x88D7 }, { 0x2F9C7, 0, 1 | DECOMP_INLINE, 0x88DE }, { 0x2F9C8, 0, 1 | DECOMP_INLINE, 0x4635 }, { 0x2F9C9, 0, 1 | DECOMP_INLINE, 0x88FA }, { 0x2F9CA, 0, 1 | DECOMP_INLINE, 0x34BB }, { 0x2F9CB, 0, 1, 5116 }, { 0x2F9CC, 0, 1, 5117 }, { 0x2F9CD, 0, 1 | DECOMP_INLINE, 0x46BE }, { 0x2F9CE, 0, 1 | DECOMP_INLINE, 0x46C7 }, { 0x2F9CF, 0, 1 | DECOMP_INLINE, 0x8AA0 }, { 0x2F9D0, 0, 1 | DECOMP_INLINE, 0x8AED }, { 0x2F9D1, 0, 1 | DECOMP_INLINE, 0x8B8A }, { 0x2F9D2, 0, 1 | DECOMP_INLINE, 0x8C55 }, { 0x2F9D3, 0, 1, 5118 }, { 0x2F9D4, 0, 1 | DECOMP_INLINE, 0x8CAB }, { 0x2F9D5, 0, 1 | DECOMP_INLINE, 0x8CC1 }, { 0x2F9D6, 0, 1 | DECOMP_INLINE, 0x8D1B }, { 0x2F9D7, 0, 1 | DECOMP_INLINE, 0x8D77 }, { 0x2F9D8, 0, 1, 5119 }, { 0x2F9D9, 0, 1, 5120 }, { 0x2F9DA, 0, 1 | DECOMP_INLINE, 0x8DCB }, { 0x2F9DB, 0, 1 | DECOMP_INLINE, 0x8DBC }, { 0x2F9DC, 0, 1 | DECOMP_INLINE, 0x8DF0 }, { 0x2F9DD, 0, 1, 5121 }, { 0x2F9DE, 0, 1 | DECOMP_INLINE, 0x8ED4 }, { 0x2F9DF, 0, 1 | DECOMP_INLINE, 0x8F38 }, { 0x2F9E0, 0, 1, 5122 }, { 0x2F9E1, 0, 1, 5123 }, { 0x2F9E2, 0, 1 | DECOMP_INLINE, 0x9094 }, { 0x2F9E3, 0, 1 | DECOMP_INLINE, 0x90F1 }, { 0x2F9E4, 0, 1 | DECOMP_INLINE, 0x9111 }, { 0x2F9E5, 0, 1, 5124 }, { 0x2F9E6, 0, 1 | DECOMP_INLINE, 0x911B }, { 0x2F9E7, 0, 1 | DECOMP_INLINE, 0x9238 }, { 0x2F9E8, 0, 1 | DECOMP_INLINE, 0x92D7 }, { 0x2F9E9, 0, 1 | DECOMP_INLINE, 0x92D8 }, { 0x2F9EA, 0, 1 | DECOMP_INLINE, 0x927C }, { 0x2F9EB, 0, 1 | DECOMP_INLINE, 0x93F9 }, { 0x2F9EC, 0, 1 | DECOMP_INLINE, 0x9415 }, { 0x2F9ED, 0, 1, 5125 }, { 0x2F9EE, 0, 1 | DECOMP_INLINE, 0x958B }, { 0x2F9EF, 0, 1 | DECOMP_INLINE, 0x4995 }, { 0x2F9F0, 0, 1 | DECOMP_INLINE, 0x95B7 }, { 0x2F9F1, 0, 1, 5126 }, { 0x2F9F2, 0, 1 | DECOMP_INLINE, 0x49E6 }, { 0x2F9F3, 0, 1 | DECOMP_INLINE, 0x96C3 }, { 0x2F9F4, 0, 1 | DECOMP_INLINE, 0x5DB2 }, { 0x2F9F5, 0, 1 | DECOMP_INLINE, 0x9723 }, { 0x2F9F6, 0, 1, 5127 }, { 0x2F9F7, 0, 1, 5128 }, { 0x2F9F8, 0, 1 | DECOMP_INLINE, 0x4A6E }, { 0x2F9F9, 0, 1 | DECOMP_INLINE, 0x4A76 }, { 0x2F9FA, 0, 1 | DECOMP_INLINE, 0x97E0 }, { 0x2F9FB, 0, 1, 5129 }, { 0x2F9FC, 0, 1 | DECOMP_INLINE, 0x4AB2 }, { 0x2F9FD, 0, 1, 5130 }, { 0x2F9FE, 0, 1 | DECOMP_INLINE, 0x980B }, { 0x2F9FF, 0, 1 | DECOMP_INLINE, 0x980B }, { 0x2FA00, 0, 1 | DECOMP_INLINE, 0x9829 }, { 0x2FA01, 0, 1, 5131 }, { 0x2FA02, 0, 1 | DECOMP_INLINE, 0x98E2 }, { 0x2FA03, 0, 1 | DECOMP_INLINE, 0x4B33 }, { 0x2FA04, 0, 1 | DECOMP_INLINE, 0x9929 }, { 0x2FA05, 0, 1 | DECOMP_INLINE, 0x99A7 }, { 0x2FA06, 0, 1 | DECOMP_INLINE, 0x99C2 }, { 0x2FA07, 0, 1 | DECOMP_INLINE, 0x99FE }, { 0x2FA08, 0, 1 | DECOMP_INLINE, 0x4BCE }, { 0x2FA09, 0, 1, 5132 }, { 0x2FA0A, 0, 1 | DECOMP_INLINE, 0x9B12 }, { 0x2FA0B, 0, 1 | DECOMP_INLINE, 0x9C40 }, { 0x2FA0C, 0, 1 | DECOMP_INLINE, 0x9CFD }, { 0x2FA0D, 0, 1 | DECOMP_INLINE, 0x4CCE }, { 0x2FA0E, 0, 1 | DECOMP_INLINE, 0x4CED }, { 0x2FA0F, 0, 1 | DECOMP_INLINE, 0x9D67 }, { 0x2FA10, 0, 1, 5133 }, { 0x2FA11, 0, 1 | DECOMP_INLINE, 0x4CF8 }, { 0x2FA12, 0, 1, 5134 }, { 0x2FA13, 0, 1, 5135 }, { 0x2FA14, 0, 1, 5136 }, { 0x2FA15, 0, 1 | DECOMP_INLINE, 0x9EBB }, { 0x2FA16, 0, 1 | DECOMP_INLINE, 0x4D56 }, { 0x2FA17, 0, 1 | DECOMP_INLINE, 0x9EF9 }, { 0x2FA18, 0, 1 | DECOMP_INLINE, 0x9EFE }, { 0x2FA19, 0, 1 | DECOMP_INLINE, 0x9F05 }, { 0x2FA1A, 0, 1 | DECOMP_INLINE, 0x9F0F }, { 0x2FA1B, 0, 1 | DECOMP_INLINE, 0x9F16 }, { 0x2FA1C, 0, 1 | DECOMP_INLINE, 0x9F3B }, { 0x2FA1D, 0, 1, 5137 } }; /* codepoints array */ static const uint32 UnicodeDecomp_codepoints[5138] = { /* 0 */ 0x0020, 0x0308, /* 2 */ 0x0020, 0x0304, /* 4 */ 0x0020, 0x0301, /* 6 */ 0x0020, 0x0327, /* 8 */ 0x0031, 0x2044, 0x0034, /* 11 */ 0x0031, 0x2044, 0x0032, /* 14 */ 0x0033, 0x2044, 0x0034, /* 17 */ 0x0041, 0x0300, /* 19 */ 0x0041, 0x0301, /* 21 */ 0x0041, 0x0302, /* 23 */ 0x0041, 0x0303, /* 25 */ 0x0041, 0x0308, /* 27 */ 0x0041, 0x030A, /* 29 */ 0x0043, 0x0327, /* 31 */ 0x0045, 0x0300, /* 33 */ 0x0045, 0x0301, /* 35 */ 0x0045, 0x0302, /* 37 */ 0x0045, 0x0308, /* 39 */ 0x0049, 0x0300, /* 41 */ 0x0049, 0x0301, /* 43 */ 0x0049, 0x0302, /* 45 */ 0x0049, 0x0308, /* 47 */ 0x004E, 0x0303, /* 49 */ 0x004F, 0x0300, /* 51 */ 0x004F, 0x0301, /* 53 */ 0x004F, 0x0302, /* 55 */ 0x004F, 0x0303, /* 57 */ 0x004F, 0x0308, /* 59 */ 0x0055, 0x0300, /* 61 */ 0x0055, 0x0301, /* 63 */ 0x0055, 0x0302, /* 65 */ 0x0055, 0x0308, /* 67 */ 0x0059, 0x0301, /* 69 */ 0x0061, 0x0300, /* 71 */ 0x0061, 0x0301, /* 73 */ 0x0061, 0x0302, /* 75 */ 0x0061, 0x0303, /* 77 */ 0x0061, 0x0308, /* 79 */ 0x0061, 0x030A, /* 81 */ 0x0063, 0x0327, /* 83 */ 0x0065, 0x0300, /* 85 */ 0x0065, 0x0301, /* 87 */ 0x0065, 0x0302, /* 89 */ 0x0065, 0x0308, /* 91 */ 0x0069, 0x0300, /* 93 */ 0x0069, 0x0301, /* 95 */ 0x0069, 0x0302, /* 97 */ 0x0069, 0x0308, /* 99 */ 0x006E, 0x0303, /* 101 */ 0x006F, 0x0300, /* 103 */ 0x006F, 0x0301, /* 105 */ 0x006F, 0x0302, /* 107 */ 0x006F, 0x0303, /* 109 */ 0x006F, 0x0308, /* 111 */ 0x0075, 0x0300, /* 113 */ 0x0075, 0x0301, /* 115 */ 0x0075, 0x0302, /* 117 */ 0x0075, 0x0308, /* 119 */ 0x0079, 0x0301, /* 121 */ 0x0079, 0x0308, /* 123 */ 0x0041, 0x0304, /* 125 */ 0x0061, 0x0304, /* 127 */ 0x0041, 0x0306, /* 129 */ 0x0061, 0x0306, /* 131 */ 0x0041, 0x0328, /* 133 */ 0x0061, 0x0328, /* 135 */ 0x0043, 0x0301, /* 137 */ 0x0063, 0x0301, /* 139 */ 0x0043, 0x0302, /* 141 */ 0x0063, 0x0302, /* 143 */ 0x0043, 0x0307, /* 145 */ 0x0063, 0x0307, /* 147 */ 0x0043, 0x030C, /* 149 */ 0x0063, 0x030C, /* 151 */ 0x0044, 0x030C, /* 153 */ 0x0064, 0x030C, /* 155 */ 0x0045, 0x0304, /* 157 */ 0x0065, 0x0304, /* 159 */ 0x0045, 0x0306, /* 161 */ 0x0065, 0x0306, /* 163 */ 0x0045, 0x0307, /* 165 */ 0x0065, 0x0307, /* 167 */ 0x0045, 0x0328, /* 169 */ 0x0065, 0x0328, /* 171 */ 0x0045, 0x030C, /* 173 */ 0x0065, 0x030C, /* 175 */ 0x0047, 0x0302, /* 177 */ 0x0067, 0x0302, /* 179 */ 0x0047, 0x0306, /* 181 */ 0x0067, 0x0306, /* 183 */ 0x0047, 0x0307, /* 185 */ 0x0067, 0x0307, /* 187 */ 0x0047, 0x0327, /* 189 */ 0x0067, 0x0327, /* 191 */ 0x0048, 0x0302, /* 193 */ 0x0068, 0x0302, /* 195 */ 0x0049, 0x0303, /* 197 */ 0x0069, 0x0303, /* 199 */ 0x0049, 0x0304, /* 201 */ 0x0069, 0x0304, /* 203 */ 0x0049, 0x0306, /* 205 */ 0x0069, 0x0306, /* 207 */ 0x0049, 0x0328, /* 209 */ 0x0069, 0x0328, /* 211 */ 0x0049, 0x0307, /* 213 */ 0x0049, 0x004A, /* 215 */ 0x0069, 0x006A, /* 217 */ 0x004A, 0x0302, /* 219 */ 0x006A, 0x0302, /* 221 */ 0x004B, 0x0327, /* 223 */ 0x006B, 0x0327, /* 225 */ 0x004C, 0x0301, /* 227 */ 0x006C, 0x0301, /* 229 */ 0x004C, 0x0327, /* 231 */ 0x006C, 0x0327, /* 233 */ 0x004C, 0x030C, /* 235 */ 0x006C, 0x030C, /* 237 */ 0x004C, 0x00B7, /* 239 */ 0x006C, 0x00B7, /* 241 */ 0x004E, 0x0301, /* 243 */ 0x006E, 0x0301, /* 245 */ 0x004E, 0x0327, /* 247 */ 0x006E, 0x0327, /* 249 */ 0x004E, 0x030C, /* 251 */ 0x006E, 0x030C, /* 253 */ 0x02BC, 0x006E, /* 255 */ 0x004F, 0x0304, /* 257 */ 0x006F, 0x0304, /* 259 */ 0x004F, 0x0306, /* 261 */ 0x006F, 0x0306, /* 263 */ 0x004F, 0x030B, /* 265 */ 0x006F, 0x030B, /* 267 */ 0x0052, 0x0301, /* 269 */ 0x0072, 0x0301, /* 271 */ 0x0052, 0x0327, /* 273 */ 0x0072, 0x0327, /* 275 */ 0x0052, 0x030C, /* 277 */ 0x0072, 0x030C, /* 279 */ 0x0053, 0x0301, /* 281 */ 0x0073, 0x0301, /* 283 */ 0x0053, 0x0302, /* 285 */ 0x0073, 0x0302, /* 287 */ 0x0053, 0x0327, /* 289 */ 0x0073, 0x0327, /* 291 */ 0x0053, 0x030C, /* 293 */ 0x0073, 0x030C, /* 295 */ 0x0054, 0x0327, /* 297 */ 0x0074, 0x0327, /* 299 */ 0x0054, 0x030C, /* 301 */ 0x0074, 0x030C, /* 303 */ 0x0055, 0x0303, /* 305 */ 0x0075, 0x0303, /* 307 */ 0x0055, 0x0304, /* 309 */ 0x0075, 0x0304, /* 311 */ 0x0055, 0x0306, /* 313 */ 0x0075, 0x0306, /* 315 */ 0x0055, 0x030A, /* 317 */ 0x0075, 0x030A, /* 319 */ 0x0055, 0x030B, /* 321 */ 0x0075, 0x030B, /* 323 */ 0x0055, 0x0328, /* 325 */ 0x0075, 0x0328, /* 327 */ 0x0057, 0x0302, /* 329 */ 0x0077, 0x0302, /* 331 */ 0x0059, 0x0302, /* 333 */ 0x0079, 0x0302, /* 335 */ 0x0059, 0x0308, /* 337 */ 0x005A, 0x0301, /* 339 */ 0x007A, 0x0301, /* 341 */ 0x005A, 0x0307, /* 343 */ 0x007A, 0x0307, /* 345 */ 0x005A, 0x030C, /* 347 */ 0x007A, 0x030C, /* 349 */ 0x004F, 0x031B, /* 351 */ 0x006F, 0x031B, /* 353 */ 0x0055, 0x031B, /* 355 */ 0x0075, 0x031B, /* 357 */ 0x0044, 0x017D, /* 359 */ 0x0044, 0x017E, /* 361 */ 0x0064, 0x017E, /* 363 */ 0x004C, 0x004A, /* 365 */ 0x004C, 0x006A, /* 367 */ 0x006C, 0x006A, /* 369 */ 0x004E, 0x004A, /* 371 */ 0x004E, 0x006A, /* 373 */ 0x006E, 0x006A, /* 375 */ 0x0041, 0x030C, /* 377 */ 0x0061, 0x030C, /* 379 */ 0x0049, 0x030C, /* 381 */ 0x0069, 0x030C, /* 383 */ 0x004F, 0x030C, /* 385 */ 0x006F, 0x030C, /* 387 */ 0x0055, 0x030C, /* 389 */ 0x0075, 0x030C, /* 391 */ 0x00DC, 0x0304, /* 393 */ 0x00FC, 0x0304, /* 395 */ 0x00DC, 0x0301, /* 397 */ 0x00FC, 0x0301, /* 399 */ 0x00DC, 0x030C, /* 401 */ 0x00FC, 0x030C, /* 403 */ 0x00DC, 0x0300, /* 405 */ 0x00FC, 0x0300, /* 407 */ 0x00C4, 0x0304, /* 409 */ 0x00E4, 0x0304, /* 411 */ 0x0226, 0x0304, /* 413 */ 0x0227, 0x0304, /* 415 */ 0x00C6, 0x0304, /* 417 */ 0x00E6, 0x0304, /* 419 */ 0x0047, 0x030C, /* 421 */ 0x0067, 0x030C, /* 423 */ 0x004B, 0x030C, /* 425 */ 0x006B, 0x030C, /* 427 */ 0x004F, 0x0328, /* 429 */ 0x006F, 0x0328, /* 431 */ 0x01EA, 0x0304, /* 433 */ 0x01EB, 0x0304, /* 435 */ 0x01B7, 0x030C, /* 437 */ 0x0292, 0x030C, /* 439 */ 0x006A, 0x030C, /* 441 */ 0x0044, 0x005A, /* 443 */ 0x0044, 0x007A, /* 445 */ 0x0064, 0x007A, /* 447 */ 0x0047, 0x0301, /* 449 */ 0x0067, 0x0301, /* 451 */ 0x004E, 0x0300, /* 453 */ 0x006E, 0x0300, /* 455 */ 0x00C5, 0x0301, /* 457 */ 0x00E5, 0x0301, /* 459 */ 0x00C6, 0x0301, /* 461 */ 0x00E6, 0x0301, /* 463 */ 0x00D8, 0x0301, /* 465 */ 0x00F8, 0x0301, /* 467 */ 0x0041, 0x030F, /* 469 */ 0x0061, 0x030F, /* 471 */ 0x0041, 0x0311, /* 473 */ 0x0061, 0x0311, /* 475 */ 0x0045, 0x030F, /* 477 */ 0x0065, 0x030F, /* 479 */ 0x0045, 0x0311, /* 481 */ 0x0065, 0x0311, /* 483 */ 0x0049, 0x030F, /* 485 */ 0x0069, 0x030F, /* 487 */ 0x0049, 0x0311, /* 489 */ 0x0069, 0x0311, /* 491 */ 0x004F, 0x030F, /* 493 */ 0x006F, 0x030F, /* 495 */ 0x004F, 0x0311, /* 497 */ 0x006F, 0x0311, /* 499 */ 0x0052, 0x030F, /* 501 */ 0x0072, 0x030F, /* 503 */ 0x0052, 0x0311, /* 505 */ 0x0072, 0x0311, /* 507 */ 0x0055, 0x030F, /* 509 */ 0x0075, 0x030F, /* 511 */ 0x0055, 0x0311, /* 513 */ 0x0075, 0x0311, /* 515 */ 0x0053, 0x0326, /* 517 */ 0x0073, 0x0326, /* 519 */ 0x0054, 0x0326, /* 521 */ 0x0074, 0x0326, /* 523 */ 0x0048, 0x030C, /* 525 */ 0x0068, 0x030C, /* 527 */ 0x0041, 0x0307, /* 529 */ 0x0061, 0x0307, /* 531 */ 0x0045, 0x0327, /* 533 */ 0x0065, 0x0327, /* 535 */ 0x00D6, 0x0304, /* 537 */ 0x00F6, 0x0304, /* 539 */ 0x00D5, 0x0304, /* 541 */ 0x00F5, 0x0304, /* 543 */ 0x004F, 0x0307, /* 545 */ 0x006F, 0x0307, /* 547 */ 0x022E, 0x0304, /* 549 */ 0x022F, 0x0304, /* 551 */ 0x0059, 0x0304, /* 553 */ 0x0079, 0x0304, /* 555 */ 0x0020, 0x0306, /* 557 */ 0x0020, 0x0307, /* 559 */ 0x0020, 0x030A, /* 561 */ 0x0020, 0x0328, /* 563 */ 0x0020, 0x0303, /* 565 */ 0x0020, 0x030B, /* 567 */ 0x0308, 0x0301, /* 569 */ 0x0020, 0x0345, /* 571 */ 0x0020, 0x0301, /* 573 */ 0x00A8, 0x0301, /* 575 */ 0x0391, 0x0301, /* 577 */ 0x0395, 0x0301, /* 579 */ 0x0397, 0x0301, /* 581 */ 0x0399, 0x0301, /* 583 */ 0x039F, 0x0301, /* 585 */ 0x03A5, 0x0301, /* 587 */ 0x03A9, 0x0301, /* 589 */ 0x03CA, 0x0301, /* 591 */ 0x0399, 0x0308, /* 593 */ 0x03A5, 0x0308, /* 595 */ 0x03B1, 0x0301, /* 597 */ 0x03B5, 0x0301, /* 599 */ 0x03B7, 0x0301, /* 601 */ 0x03B9, 0x0301, /* 603 */ 0x03CB, 0x0301, /* 605 */ 0x03B9, 0x0308, /* 607 */ 0x03C5, 0x0308, /* 609 */ 0x03BF, 0x0301, /* 611 */ 0x03C5, 0x0301, /* 613 */ 0x03C9, 0x0301, /* 615 */ 0x03D2, 0x0301, /* 617 */ 0x03D2, 0x0308, /* 619 */ 0x0415, 0x0300, /* 621 */ 0x0415, 0x0308, /* 623 */ 0x0413, 0x0301, /* 625 */ 0x0406, 0x0308, /* 627 */ 0x041A, 0x0301, /* 629 */ 0x0418, 0x0300, /* 631 */ 0x0423, 0x0306, /* 633 */ 0x0418, 0x0306, /* 635 */ 0x0438, 0x0306, /* 637 */ 0x0435, 0x0300, /* 639 */ 0x0435, 0x0308, /* 641 */ 0x0433, 0x0301, /* 643 */ 0x0456, 0x0308, /* 645 */ 0x043A, 0x0301, /* 647 */ 0x0438, 0x0300, /* 649 */ 0x0443, 0x0306, /* 651 */ 0x0474, 0x030F, /* 653 */ 0x0475, 0x030F, /* 655 */ 0x0416, 0x0306, /* 657 */ 0x0436, 0x0306, /* 659 */ 0x0410, 0x0306, /* 661 */ 0x0430, 0x0306, /* 663 */ 0x0410, 0x0308, /* 665 */ 0x0430, 0x0308, /* 667 */ 0x0415, 0x0306, /* 669 */ 0x0435, 0x0306, /* 671 */ 0x04D8, 0x0308, /* 673 */ 0x04D9, 0x0308, /* 675 */ 0x0416, 0x0308, /* 677 */ 0x0436, 0x0308, /* 679 */ 0x0417, 0x0308, /* 681 */ 0x0437, 0x0308, /* 683 */ 0x0418, 0x0304, /* 685 */ 0x0438, 0x0304, /* 687 */ 0x0418, 0x0308, /* 689 */ 0x0438, 0x0308, /* 691 */ 0x041E, 0x0308, /* 693 */ 0x043E, 0x0308, /* 695 */ 0x04E8, 0x0308, /* 697 */ 0x04E9, 0x0308, /* 699 */ 0x042D, 0x0308, /* 701 */ 0x044D, 0x0308, /* 703 */ 0x0423, 0x0304, /* 705 */ 0x0443, 0x0304, /* 707 */ 0x0423, 0x0308, /* 709 */ 0x0443, 0x0308, /* 711 */ 0x0423, 0x030B, /* 713 */ 0x0443, 0x030B, /* 715 */ 0x0427, 0x0308, /* 717 */ 0x0447, 0x0308, /* 719 */ 0x042B, 0x0308, /* 721 */ 0x044B, 0x0308, /* 723 */ 0x0565, 0x0582, /* 725 */ 0x0627, 0x0653, /* 727 */ 0x0627, 0x0654, /* 729 */ 0x0648, 0x0654, /* 731 */ 0x0627, 0x0655, /* 733 */ 0x064A, 0x0654, /* 735 */ 0x0627, 0x0674, /* 737 */ 0x0648, 0x0674, /* 739 */ 0x06C7, 0x0674, /* 741 */ 0x064A, 0x0674, /* 743 */ 0x06D5, 0x0654, /* 745 */ 0x06C1, 0x0654, /* 747 */ 0x06D2, 0x0654, /* 749 */ 0x0928, 0x093C, /* 751 */ 0x0930, 0x093C, /* 753 */ 0x0933, 0x093C, /* 755 */ 0x0915, 0x093C, /* 757 */ 0x0916, 0x093C, /* 759 */ 0x0917, 0x093C, /* 761 */ 0x091C, 0x093C, /* 763 */ 0x0921, 0x093C, /* 765 */ 0x0922, 0x093C, /* 767 */ 0x092B, 0x093C, /* 769 */ 0x092F, 0x093C, /* 771 */ 0x09C7, 0x09BE, /* 773 */ 0x09C7, 0x09D7, /* 775 */ 0x09A1, 0x09BC, /* 777 */ 0x09A2, 0x09BC, /* 779 */ 0x09AF, 0x09BC, /* 781 */ 0x0A32, 0x0A3C, /* 783 */ 0x0A38, 0x0A3C, /* 785 */ 0x0A16, 0x0A3C, /* 787 */ 0x0A17, 0x0A3C, /* 789 */ 0x0A1C, 0x0A3C, /* 791 */ 0x0A2B, 0x0A3C, /* 793 */ 0x0B47, 0x0B56, /* 795 */ 0x0B47, 0x0B3E, /* 797 */ 0x0B47, 0x0B57, /* 799 */ 0x0B21, 0x0B3C, /* 801 */ 0x0B22, 0x0B3C, /* 803 */ 0x0B92, 0x0BD7, /* 805 */ 0x0BC6, 0x0BBE, /* 807 */ 0x0BC7, 0x0BBE, /* 809 */ 0x0BC6, 0x0BD7, /* 811 */ 0x0C46, 0x0C56, /* 813 */ 0x0CBF, 0x0CD5, /* 815 */ 0x0CC6, 0x0CD5, /* 817 */ 0x0CC6, 0x0CD6, /* 819 */ 0x0CC6, 0x0CC2, /* 821 */ 0x0CCA, 0x0CD5, /* 823 */ 0x0D46, 0x0D3E, /* 825 */ 0x0D47, 0x0D3E, /* 827 */ 0x0D46, 0x0D57, /* 829 */ 0x0DD9, 0x0DCA, /* 831 */ 0x0DD9, 0x0DCF, /* 833 */ 0x0DDC, 0x0DCA, /* 835 */ 0x0DD9, 0x0DDF, /* 837 */ 0x0E4D, 0x0E32, /* 839 */ 0x0ECD, 0x0EB2, /* 841 */ 0x0EAB, 0x0E99, /* 843 */ 0x0EAB, 0x0EA1, /* 845 */ 0x0F42, 0x0FB7, /* 847 */ 0x0F4C, 0x0FB7, /* 849 */ 0x0F51, 0x0FB7, /* 851 */ 0x0F56, 0x0FB7, /* 853 */ 0x0F5B, 0x0FB7, /* 855 */ 0x0F40, 0x0FB5, /* 857 */ 0x0F71, 0x0F72, /* 859 */ 0x0F71, 0x0F74, /* 861 */ 0x0FB2, 0x0F80, /* 863 */ 0x0FB2, 0x0F81, /* 865 */ 0x0FB3, 0x0F80, /* 867 */ 0x0FB3, 0x0F81, /* 869 */ 0x0F71, 0x0F80, /* 871 */ 0x0F92, 0x0FB7, /* 873 */ 0x0F9C, 0x0FB7, /* 875 */ 0x0FA1, 0x0FB7, /* 877 */ 0x0FA6, 0x0FB7, /* 879 */ 0x0FAB, 0x0FB7, /* 881 */ 0x0F90, 0x0FB5, /* 883 */ 0x1025, 0x102E, /* 885 */ 0x1B05, 0x1B35, /* 887 */ 0x1B07, 0x1B35, /* 889 */ 0x1B09, 0x1B35, /* 891 */ 0x1B0B, 0x1B35, /* 893 */ 0x1B0D, 0x1B35, /* 895 */ 0x1B11, 0x1B35, /* 897 */ 0x1B3A, 0x1B35, /* 899 */ 0x1B3C, 0x1B35, /* 901 */ 0x1B3E, 0x1B35, /* 903 */ 0x1B3F, 0x1B35, /* 905 */ 0x1B42, 0x1B35, /* 907 */ 0x0041, 0x0325, /* 909 */ 0x0061, 0x0325, /* 911 */ 0x0042, 0x0307, /* 913 */ 0x0062, 0x0307, /* 915 */ 0x0042, 0x0323, /* 917 */ 0x0062, 0x0323, /* 919 */ 0x0042, 0x0331, /* 921 */ 0x0062, 0x0331, /* 923 */ 0x00C7, 0x0301, /* 925 */ 0x00E7, 0x0301, /* 927 */ 0x0044, 0x0307, /* 929 */ 0x0064, 0x0307, /* 931 */ 0x0044, 0x0323, /* 933 */ 0x0064, 0x0323, /* 935 */ 0x0044, 0x0331, /* 937 */ 0x0064, 0x0331, /* 939 */ 0x0044, 0x0327, /* 941 */ 0x0064, 0x0327, /* 943 */ 0x0044, 0x032D, /* 945 */ 0x0064, 0x032D, /* 947 */ 0x0112, 0x0300, /* 949 */ 0x0113, 0x0300, /* 951 */ 0x0112, 0x0301, /* 953 */ 0x0113, 0x0301, /* 955 */ 0x0045, 0x032D, /* 957 */ 0x0065, 0x032D, /* 959 */ 0x0045, 0x0330, /* 961 */ 0x0065, 0x0330, /* 963 */ 0x0228, 0x0306, /* 965 */ 0x0229, 0x0306, /* 967 */ 0x0046, 0x0307, /* 969 */ 0x0066, 0x0307, /* 971 */ 0x0047, 0x0304, /* 973 */ 0x0067, 0x0304, /* 975 */ 0x0048, 0x0307, /* 977 */ 0x0068, 0x0307, /* 979 */ 0x0048, 0x0323, /* 981 */ 0x0068, 0x0323, /* 983 */ 0x0048, 0x0308, /* 985 */ 0x0068, 0x0308, /* 987 */ 0x0048, 0x0327, /* 989 */ 0x0068, 0x0327, /* 991 */ 0x0048, 0x032E, /* 993 */ 0x0068, 0x032E, /* 995 */ 0x0049, 0x0330, /* 997 */ 0x0069, 0x0330, /* 999 */ 0x00CF, 0x0301, /* 1001 */ 0x00EF, 0x0301, /* 1003 */ 0x004B, 0x0301, /* 1005 */ 0x006B, 0x0301, /* 1007 */ 0x004B, 0x0323, /* 1009 */ 0x006B, 0x0323, /* 1011 */ 0x004B, 0x0331, /* 1013 */ 0x006B, 0x0331, /* 1015 */ 0x004C, 0x0323, /* 1017 */ 0x006C, 0x0323, /* 1019 */ 0x1E36, 0x0304, /* 1021 */ 0x1E37, 0x0304, /* 1023 */ 0x004C, 0x0331, /* 1025 */ 0x006C, 0x0331, /* 1027 */ 0x004C, 0x032D, /* 1029 */ 0x006C, 0x032D, /* 1031 */ 0x004D, 0x0301, /* 1033 */ 0x006D, 0x0301, /* 1035 */ 0x004D, 0x0307, /* 1037 */ 0x006D, 0x0307, /* 1039 */ 0x004D, 0x0323, /* 1041 */ 0x006D, 0x0323, /* 1043 */ 0x004E, 0x0307, /* 1045 */ 0x006E, 0x0307, /* 1047 */ 0x004E, 0x0323, /* 1049 */ 0x006E, 0x0323, /* 1051 */ 0x004E, 0x0331, /* 1053 */ 0x006E, 0x0331, /* 1055 */ 0x004E, 0x032D, /* 1057 */ 0x006E, 0x032D, /* 1059 */ 0x00D5, 0x0301, /* 1061 */ 0x00F5, 0x0301, /* 1063 */ 0x00D5, 0x0308, /* 1065 */ 0x00F5, 0x0308, /* 1067 */ 0x014C, 0x0300, /* 1069 */ 0x014D, 0x0300, /* 1071 */ 0x014C, 0x0301, /* 1073 */ 0x014D, 0x0301, /* 1075 */ 0x0050, 0x0301, /* 1077 */ 0x0070, 0x0301, /* 1079 */ 0x0050, 0x0307, /* 1081 */ 0x0070, 0x0307, /* 1083 */ 0x0052, 0x0307, /* 1085 */ 0x0072, 0x0307, /* 1087 */ 0x0052, 0x0323, /* 1089 */ 0x0072, 0x0323, /* 1091 */ 0x1E5A, 0x0304, /* 1093 */ 0x1E5B, 0x0304, /* 1095 */ 0x0052, 0x0331, /* 1097 */ 0x0072, 0x0331, /* 1099 */ 0x0053, 0x0307, /* 1101 */ 0x0073, 0x0307, /* 1103 */ 0x0053, 0x0323, /* 1105 */ 0x0073, 0x0323, /* 1107 */ 0x015A, 0x0307, /* 1109 */ 0x015B, 0x0307, /* 1111 */ 0x0160, 0x0307, /* 1113 */ 0x0161, 0x0307, /* 1115 */ 0x1E62, 0x0307, /* 1117 */ 0x1E63, 0x0307, /* 1119 */ 0x0054, 0x0307, /* 1121 */ 0x0074, 0x0307, /* 1123 */ 0x0054, 0x0323, /* 1125 */ 0x0074, 0x0323, /* 1127 */ 0x0054, 0x0331, /* 1129 */ 0x0074, 0x0331, /* 1131 */ 0x0054, 0x032D, /* 1133 */ 0x0074, 0x032D, /* 1135 */ 0x0055, 0x0324, /* 1137 */ 0x0075, 0x0324, /* 1139 */ 0x0055, 0x0330, /* 1141 */ 0x0075, 0x0330, /* 1143 */ 0x0055, 0x032D, /* 1145 */ 0x0075, 0x032D, /* 1147 */ 0x0168, 0x0301, /* 1149 */ 0x0169, 0x0301, /* 1151 */ 0x016A, 0x0308, /* 1153 */ 0x016B, 0x0308, /* 1155 */ 0x0056, 0x0303, /* 1157 */ 0x0076, 0x0303, /* 1159 */ 0x0056, 0x0323, /* 1161 */ 0x0076, 0x0323, /* 1163 */ 0x0057, 0x0300, /* 1165 */ 0x0077, 0x0300, /* 1167 */ 0x0057, 0x0301, /* 1169 */ 0x0077, 0x0301, /* 1171 */ 0x0057, 0x0308, /* 1173 */ 0x0077, 0x0308, /* 1175 */ 0x0057, 0x0307, /* 1177 */ 0x0077, 0x0307, /* 1179 */ 0x0057, 0x0323, /* 1181 */ 0x0077, 0x0323, /* 1183 */ 0x0058, 0x0307, /* 1185 */ 0x0078, 0x0307, /* 1187 */ 0x0058, 0x0308, /* 1189 */ 0x0078, 0x0308, /* 1191 */ 0x0059, 0x0307, /* 1193 */ 0x0079, 0x0307, /* 1195 */ 0x005A, 0x0302, /* 1197 */ 0x007A, 0x0302, /* 1199 */ 0x005A, 0x0323, /* 1201 */ 0x007A, 0x0323, /* 1203 */ 0x005A, 0x0331, /* 1205 */ 0x007A, 0x0331, /* 1207 */ 0x0068, 0x0331, /* 1209 */ 0x0074, 0x0308, /* 1211 */ 0x0077, 0x030A, /* 1213 */ 0x0079, 0x030A, /* 1215 */ 0x0061, 0x02BE, /* 1217 */ 0x017F, 0x0307, /* 1219 */ 0x0041, 0x0323, /* 1221 */ 0x0061, 0x0323, /* 1223 */ 0x0041, 0x0309, /* 1225 */ 0x0061, 0x0309, /* 1227 */ 0x00C2, 0x0301, /* 1229 */ 0x00E2, 0x0301, /* 1231 */ 0x00C2, 0x0300, /* 1233 */ 0x00E2, 0x0300, /* 1235 */ 0x00C2, 0x0309, /* 1237 */ 0x00E2, 0x0309, /* 1239 */ 0x00C2, 0x0303, /* 1241 */ 0x00E2, 0x0303, /* 1243 */ 0x1EA0, 0x0302, /* 1245 */ 0x1EA1, 0x0302, /* 1247 */ 0x0102, 0x0301, /* 1249 */ 0x0103, 0x0301, /* 1251 */ 0x0102, 0x0300, /* 1253 */ 0x0103, 0x0300, /* 1255 */ 0x0102, 0x0309, /* 1257 */ 0x0103, 0x0309, /* 1259 */ 0x0102, 0x0303, /* 1261 */ 0x0103, 0x0303, /* 1263 */ 0x1EA0, 0x0306, /* 1265 */ 0x1EA1, 0x0306, /* 1267 */ 0x0045, 0x0323, /* 1269 */ 0x0065, 0x0323, /* 1271 */ 0x0045, 0x0309, /* 1273 */ 0x0065, 0x0309, /* 1275 */ 0x0045, 0x0303, /* 1277 */ 0x0065, 0x0303, /* 1279 */ 0x00CA, 0x0301, /* 1281 */ 0x00EA, 0x0301, /* 1283 */ 0x00CA, 0x0300, /* 1285 */ 0x00EA, 0x0300, /* 1287 */ 0x00CA, 0x0309, /* 1289 */ 0x00EA, 0x0309, /* 1291 */ 0x00CA, 0x0303, /* 1293 */ 0x00EA, 0x0303, /* 1295 */ 0x1EB8, 0x0302, /* 1297 */ 0x1EB9, 0x0302, /* 1299 */ 0x0049, 0x0309, /* 1301 */ 0x0069, 0x0309, /* 1303 */ 0x0049, 0x0323, /* 1305 */ 0x0069, 0x0323, /* 1307 */ 0x004F, 0x0323, /* 1309 */ 0x006F, 0x0323, /* 1311 */ 0x004F, 0x0309, /* 1313 */ 0x006F, 0x0309, /* 1315 */ 0x00D4, 0x0301, /* 1317 */ 0x00F4, 0x0301, /* 1319 */ 0x00D4, 0x0300, /* 1321 */ 0x00F4, 0x0300, /* 1323 */ 0x00D4, 0x0309, /* 1325 */ 0x00F4, 0x0309, /* 1327 */ 0x00D4, 0x0303, /* 1329 */ 0x00F4, 0x0303, /* 1331 */ 0x1ECC, 0x0302, /* 1333 */ 0x1ECD, 0x0302, /* 1335 */ 0x01A0, 0x0301, /* 1337 */ 0x01A1, 0x0301, /* 1339 */ 0x01A0, 0x0300, /* 1341 */ 0x01A1, 0x0300, /* 1343 */ 0x01A0, 0x0309, /* 1345 */ 0x01A1, 0x0309, /* 1347 */ 0x01A0, 0x0303, /* 1349 */ 0x01A1, 0x0303, /* 1351 */ 0x01A0, 0x0323, /* 1353 */ 0x01A1, 0x0323, /* 1355 */ 0x0055, 0x0323, /* 1357 */ 0x0075, 0x0323, /* 1359 */ 0x0055, 0x0309, /* 1361 */ 0x0075, 0x0309, /* 1363 */ 0x01AF, 0x0301, /* 1365 */ 0x01B0, 0x0301, /* 1367 */ 0x01AF, 0x0300, /* 1369 */ 0x01B0, 0x0300, /* 1371 */ 0x01AF, 0x0309, /* 1373 */ 0x01B0, 0x0309, /* 1375 */ 0x01AF, 0x0303, /* 1377 */ 0x01B0, 0x0303, /* 1379 */ 0x01AF, 0x0323, /* 1381 */ 0x01B0, 0x0323, /* 1383 */ 0x0059, 0x0300, /* 1385 */ 0x0079, 0x0300, /* 1387 */ 0x0059, 0x0323, /* 1389 */ 0x0079, 0x0323, /* 1391 */ 0x0059, 0x0309, /* 1393 */ 0x0079, 0x0309, /* 1395 */ 0x0059, 0x0303, /* 1397 */ 0x0079, 0x0303, /* 1399 */ 0x03B1, 0x0313, /* 1401 */ 0x03B1, 0x0314, /* 1403 */ 0x1F00, 0x0300, /* 1405 */ 0x1F01, 0x0300, /* 1407 */ 0x1F00, 0x0301, /* 1409 */ 0x1F01, 0x0301, /* 1411 */ 0x1F00, 0x0342, /* 1413 */ 0x1F01, 0x0342, /* 1415 */ 0x0391, 0x0313, /* 1417 */ 0x0391, 0x0314, /* 1419 */ 0x1F08, 0x0300, /* 1421 */ 0x1F09, 0x0300, /* 1423 */ 0x1F08, 0x0301, /* 1425 */ 0x1F09, 0x0301, /* 1427 */ 0x1F08, 0x0342, /* 1429 */ 0x1F09, 0x0342, /* 1431 */ 0x03B5, 0x0313, /* 1433 */ 0x03B5, 0x0314, /* 1435 */ 0x1F10, 0x0300, /* 1437 */ 0x1F11, 0x0300, /* 1439 */ 0x1F10, 0x0301, /* 1441 */ 0x1F11, 0x0301, /* 1443 */ 0x0395, 0x0313, /* 1445 */ 0x0395, 0x0314, /* 1447 */ 0x1F18, 0x0300, /* 1449 */ 0x1F19, 0x0300, /* 1451 */ 0x1F18, 0x0301, /* 1453 */ 0x1F19, 0x0301, /* 1455 */ 0x03B7, 0x0313, /* 1457 */ 0x03B7, 0x0314, /* 1459 */ 0x1F20, 0x0300, /* 1461 */ 0x1F21, 0x0300, /* 1463 */ 0x1F20, 0x0301, /* 1465 */ 0x1F21, 0x0301, /* 1467 */ 0x1F20, 0x0342, /* 1469 */ 0x1F21, 0x0342, /* 1471 */ 0x0397, 0x0313, /* 1473 */ 0x0397, 0x0314, /* 1475 */ 0x1F28, 0x0300, /* 1477 */ 0x1F29, 0x0300, /* 1479 */ 0x1F28, 0x0301, /* 1481 */ 0x1F29, 0x0301, /* 1483 */ 0x1F28, 0x0342, /* 1485 */ 0x1F29, 0x0342, /* 1487 */ 0x03B9, 0x0313, /* 1489 */ 0x03B9, 0x0314, /* 1491 */ 0x1F30, 0x0300, /* 1493 */ 0x1F31, 0x0300, /* 1495 */ 0x1F30, 0x0301, /* 1497 */ 0x1F31, 0x0301, /* 1499 */ 0x1F30, 0x0342, /* 1501 */ 0x1F31, 0x0342, /* 1503 */ 0x0399, 0x0313, /* 1505 */ 0x0399, 0x0314, /* 1507 */ 0x1F38, 0x0300, /* 1509 */ 0x1F39, 0x0300, /* 1511 */ 0x1F38, 0x0301, /* 1513 */ 0x1F39, 0x0301, /* 1515 */ 0x1F38, 0x0342, /* 1517 */ 0x1F39, 0x0342, /* 1519 */ 0x03BF, 0x0313, /* 1521 */ 0x03BF, 0x0314, /* 1523 */ 0x1F40, 0x0300, /* 1525 */ 0x1F41, 0x0300, /* 1527 */ 0x1F40, 0x0301, /* 1529 */ 0x1F41, 0x0301, /* 1531 */ 0x039F, 0x0313, /* 1533 */ 0x039F, 0x0314, /* 1535 */ 0x1F48, 0x0300, /* 1537 */ 0x1F49, 0x0300, /* 1539 */ 0x1F48, 0x0301, /* 1541 */ 0x1F49, 0x0301, /* 1543 */ 0x03C5, 0x0313, /* 1545 */ 0x03C5, 0x0314, /* 1547 */ 0x1F50, 0x0300, /* 1549 */ 0x1F51, 0x0300, /* 1551 */ 0x1F50, 0x0301, /* 1553 */ 0x1F51, 0x0301, /* 1555 */ 0x1F50, 0x0342, /* 1557 */ 0x1F51, 0x0342, /* 1559 */ 0x03A5, 0x0314, /* 1561 */ 0x1F59, 0x0300, /* 1563 */ 0x1F59, 0x0301, /* 1565 */ 0x1F59, 0x0342, /* 1567 */ 0x03C9, 0x0313, /* 1569 */ 0x03C9, 0x0314, /* 1571 */ 0x1F60, 0x0300, /* 1573 */ 0x1F61, 0x0300, /* 1575 */ 0x1F60, 0x0301, /* 1577 */ 0x1F61, 0x0301, /* 1579 */ 0x1F60, 0x0342, /* 1581 */ 0x1F61, 0x0342, /* 1583 */ 0x03A9, 0x0313, /* 1585 */ 0x03A9, 0x0314, /* 1587 */ 0x1F68, 0x0300, /* 1589 */ 0x1F69, 0x0300, /* 1591 */ 0x1F68, 0x0301, /* 1593 */ 0x1F69, 0x0301, /* 1595 */ 0x1F68, 0x0342, /* 1597 */ 0x1F69, 0x0342, /* 1599 */ 0x03B1, 0x0300, /* 1601 */ 0x03B5, 0x0300, /* 1603 */ 0x03B7, 0x0300, /* 1605 */ 0x03B9, 0x0300, /* 1607 */ 0x03BF, 0x0300, /* 1609 */ 0x03C5, 0x0300, /* 1611 */ 0x03C9, 0x0300, /* 1613 */ 0x1F00, 0x0345, /* 1615 */ 0x1F01, 0x0345, /* 1617 */ 0x1F02, 0x0345, /* 1619 */ 0x1F03, 0x0345, /* 1621 */ 0x1F04, 0x0345, /* 1623 */ 0x1F05, 0x0345, /* 1625 */ 0x1F06, 0x0345, /* 1627 */ 0x1F07, 0x0345, /* 1629 */ 0x1F08, 0x0345, /* 1631 */ 0x1F09, 0x0345, /* 1633 */ 0x1F0A, 0x0345, /* 1635 */ 0x1F0B, 0x0345, /* 1637 */ 0x1F0C, 0x0345, /* 1639 */ 0x1F0D, 0x0345, /* 1641 */ 0x1F0E, 0x0345, /* 1643 */ 0x1F0F, 0x0345, /* 1645 */ 0x1F20, 0x0345, /* 1647 */ 0x1F21, 0x0345, /* 1649 */ 0x1F22, 0x0345, /* 1651 */ 0x1F23, 0x0345, /* 1653 */ 0x1F24, 0x0345, /* 1655 */ 0x1F25, 0x0345, /* 1657 */ 0x1F26, 0x0345, /* 1659 */ 0x1F27, 0x0345, /* 1661 */ 0x1F28, 0x0345, /* 1663 */ 0x1F29, 0x0345, /* 1665 */ 0x1F2A, 0x0345, /* 1667 */ 0x1F2B, 0x0345, /* 1669 */ 0x1F2C, 0x0345, /* 1671 */ 0x1F2D, 0x0345, /* 1673 */ 0x1F2E, 0x0345, /* 1675 */ 0x1F2F, 0x0345, /* 1677 */ 0x1F60, 0x0345, /* 1679 */ 0x1F61, 0x0345, /* 1681 */ 0x1F62, 0x0345, /* 1683 */ 0x1F63, 0x0345, /* 1685 */ 0x1F64, 0x0345, /* 1687 */ 0x1F65, 0x0345, /* 1689 */ 0x1F66, 0x0345, /* 1691 */ 0x1F67, 0x0345, /* 1693 */ 0x1F68, 0x0345, /* 1695 */ 0x1F69, 0x0345, /* 1697 */ 0x1F6A, 0x0345, /* 1699 */ 0x1F6B, 0x0345, /* 1701 */ 0x1F6C, 0x0345, /* 1703 */ 0x1F6D, 0x0345, /* 1705 */ 0x1F6E, 0x0345, /* 1707 */ 0x1F6F, 0x0345, /* 1709 */ 0x03B1, 0x0306, /* 1711 */ 0x03B1, 0x0304, /* 1713 */ 0x1F70, 0x0345, /* 1715 */ 0x03B1, 0x0345, /* 1717 */ 0x03AC, 0x0345, /* 1719 */ 0x03B1, 0x0342, /* 1721 */ 0x1FB6, 0x0345, /* 1723 */ 0x0391, 0x0306, /* 1725 */ 0x0391, 0x0304, /* 1727 */ 0x0391, 0x0300, /* 1729 */ 0x0391, 0x0345, /* 1731 */ 0x0020, 0x0313, /* 1733 */ 0x0020, 0x0313, /* 1735 */ 0x0020, 0x0342, /* 1737 */ 0x00A8, 0x0342, /* 1739 */ 0x1F74, 0x0345, /* 1741 */ 0x03B7, 0x0345, /* 1743 */ 0x03AE, 0x0345, /* 1745 */ 0x03B7, 0x0342, /* 1747 */ 0x1FC6, 0x0345, /* 1749 */ 0x0395, 0x0300, /* 1751 */ 0x0397, 0x0300, /* 1753 */ 0x0397, 0x0345, /* 1755 */ 0x1FBF, 0x0300, /* 1757 */ 0x1FBF, 0x0301, /* 1759 */ 0x1FBF, 0x0342, /* 1761 */ 0x03B9, 0x0306, /* 1763 */ 0x03B9, 0x0304, /* 1765 */ 0x03CA, 0x0300, /* 1767 */ 0x03B9, 0x0342, /* 1769 */ 0x03CA, 0x0342, /* 1771 */ 0x0399, 0x0306, /* 1773 */ 0x0399, 0x0304, /* 1775 */ 0x0399, 0x0300, /* 1777 */ 0x1FFE, 0x0300, /* 1779 */ 0x1FFE, 0x0301, /* 1781 */ 0x1FFE, 0x0342, /* 1783 */ 0x03C5, 0x0306, /* 1785 */ 0x03C5, 0x0304, /* 1787 */ 0x03CB, 0x0300, /* 1789 */ 0x03C1, 0x0313, /* 1791 */ 0x03C1, 0x0314, /* 1793 */ 0x03C5, 0x0342, /* 1795 */ 0x03CB, 0x0342, /* 1797 */ 0x03A5, 0x0306, /* 1799 */ 0x03A5, 0x0304, /* 1801 */ 0x03A5, 0x0300, /* 1803 */ 0x03A1, 0x0314, /* 1805 */ 0x00A8, 0x0300, /* 1807 */ 0x1F7C, 0x0345, /* 1809 */ 0x03C9, 0x0345, /* 1811 */ 0x03CE, 0x0345, /* 1813 */ 0x03C9, 0x0342, /* 1815 */ 0x1FF6, 0x0345, /* 1817 */ 0x039F, 0x0300, /* 1819 */ 0x03A9, 0x0300, /* 1821 */ 0x03A9, 0x0345, /* 1823 */ 0x0020, 0x0314, /* 1825 */ 0x0020, 0x0333, /* 1827 */ 0x002E, 0x002E, /* 1829 */ 0x002E, 0x002E, 0x002E, /* 1832 */ 0x2032, 0x2032, /* 1834 */ 0x2032, 0x2032, 0x2032, /* 1837 */ 0x2035, 0x2035, /* 1839 */ 0x2035, 0x2035, 0x2035, /* 1842 */ 0x0021, 0x0021, /* 1844 */ 0x0020, 0x0305, /* 1846 */ 0x003F, 0x003F, /* 1848 */ 0x003F, 0x0021, /* 1850 */ 0x0021, 0x003F, /* 1852 */ 0x2032, 0x2032, 0x2032, 0x2032, /* 1856 */ 0x0052, 0x0073, /* 1858 */ 0x0061, 0x002F, 0x0063, /* 1861 */ 0x0061, 0x002F, 0x0073, /* 1864 */ 0x00B0, 0x0043, /* 1866 */ 0x0063, 0x002F, 0x006F, /* 1869 */ 0x0063, 0x002F, 0x0075, /* 1872 */ 0x00B0, 0x0046, /* 1874 */ 0x004E, 0x006F, /* 1876 */ 0x0053, 0x004D, /* 1878 */ 0x0054, 0x0045, 0x004C, /* 1881 */ 0x0054, 0x004D, /* 1883 */ 0x0046, 0x0041, 0x0058, /* 1886 */ 0x0031, 0x2044, 0x0037, /* 1889 */ 0x0031, 0x2044, 0x0039, /* 1892 */ 0x0031, 0x2044, 0x0031, 0x0030, /* 1896 */ 0x0031, 0x2044, 0x0033, /* 1899 */ 0x0032, 0x2044, 0x0033, /* 1902 */ 0x0031, 0x2044, 0x0035, /* 1905 */ 0x0032, 0x2044, 0x0035, /* 1908 */ 0x0033, 0x2044, 0x0035, /* 1911 */ 0x0034, 0x2044, 0x0035, /* 1914 */ 0x0031, 0x2044, 0x0036, /* 1917 */ 0x0035, 0x2044, 0x0036, /* 1920 */ 0x0031, 0x2044, 0x0038, /* 1923 */ 0x0033, 0x2044, 0x0038, /* 1926 */ 0x0035, 0x2044, 0x0038, /* 1929 */ 0x0037, 0x2044, 0x0038, /* 1932 */ 0x0031, 0x2044, /* 1934 */ 0x0049, 0x0049, /* 1936 */ 0x0049, 0x0049, 0x0049, /* 1939 */ 0x0049, 0x0056, /* 1941 */ 0x0056, 0x0049, /* 1943 */ 0x0056, 0x0049, 0x0049, /* 1946 */ 0x0056, 0x0049, 0x0049, 0x0049, /* 1950 */ 0x0049, 0x0058, /* 1952 */ 0x0058, 0x0049, /* 1954 */ 0x0058, 0x0049, 0x0049, /* 1957 */ 0x0069, 0x0069, /* 1959 */ 0x0069, 0x0069, 0x0069, /* 1962 */ 0x0069, 0x0076, /* 1964 */ 0x0076, 0x0069, /* 1966 */ 0x0076, 0x0069, 0x0069, /* 1969 */ 0x0076, 0x0069, 0x0069, 0x0069, /* 1973 */ 0x0069, 0x0078, /* 1975 */ 0x0078, 0x0069, /* 1977 */ 0x0078, 0x0069, 0x0069, /* 1980 */ 0x0030, 0x2044, 0x0033, /* 1983 */ 0x2190, 0x0338, /* 1985 */ 0x2192, 0x0338, /* 1987 */ 0x2194, 0x0338, /* 1989 */ 0x21D0, 0x0338, /* 1991 */ 0x21D4, 0x0338, /* 1993 */ 0x21D2, 0x0338, /* 1995 */ 0x2203, 0x0338, /* 1997 */ 0x2208, 0x0338, /* 1999 */ 0x220B, 0x0338, /* 2001 */ 0x2223, 0x0338, /* 2003 */ 0x2225, 0x0338, /* 2005 */ 0x222B, 0x222B, /* 2007 */ 0x222B, 0x222B, 0x222B, /* 2010 */ 0x222E, 0x222E, /* 2012 */ 0x222E, 0x222E, 0x222E, /* 2015 */ 0x223C, 0x0338, /* 2017 */ 0x2243, 0x0338, /* 2019 */ 0x2245, 0x0338, /* 2021 */ 0x2248, 0x0338, /* 2023 */ 0x003D, 0x0338, /* 2025 */ 0x2261, 0x0338, /* 2027 */ 0x224D, 0x0338, /* 2029 */ 0x003C, 0x0338, /* 2031 */ 0x003E, 0x0338, /* 2033 */ 0x2264, 0x0338, /* 2035 */ 0x2265, 0x0338, /* 2037 */ 0x2272, 0x0338, /* 2039 */ 0x2273, 0x0338, /* 2041 */ 0x2276, 0x0338, /* 2043 */ 0x2277, 0x0338, /* 2045 */ 0x227A, 0x0338, /* 2047 */ 0x227B, 0x0338, /* 2049 */ 0x2282, 0x0338, /* 2051 */ 0x2283, 0x0338, /* 2053 */ 0x2286, 0x0338, /* 2055 */ 0x2287, 0x0338, /* 2057 */ 0x22A2, 0x0338, /* 2059 */ 0x22A8, 0x0338, /* 2061 */ 0x22A9, 0x0338, /* 2063 */ 0x22AB, 0x0338, /* 2065 */ 0x227C, 0x0338, /* 2067 */ 0x227D, 0x0338, /* 2069 */ 0x2291, 0x0338, /* 2071 */ 0x2292, 0x0338, /* 2073 */ 0x22B2, 0x0338, /* 2075 */ 0x22B3, 0x0338, /* 2077 */ 0x22B4, 0x0338, /* 2079 */ 0x22B5, 0x0338, /* 2081 */ 0x0031, 0x0030, /* 2083 */ 0x0031, 0x0031, /* 2085 */ 0x0031, 0x0032, /* 2087 */ 0x0031, 0x0033, /* 2089 */ 0x0031, 0x0034, /* 2091 */ 0x0031, 0x0035, /* 2093 */ 0x0031, 0x0036, /* 2095 */ 0x0031, 0x0037, /* 2097 */ 0x0031, 0x0038, /* 2099 */ 0x0031, 0x0039, /* 2101 */ 0x0032, 0x0030, /* 2103 */ 0x0028, 0x0031, 0x0029, /* 2106 */ 0x0028, 0x0032, 0x0029, /* 2109 */ 0x0028, 0x0033, 0x0029, /* 2112 */ 0x0028, 0x0034, 0x0029, /* 2115 */ 0x0028, 0x0035, 0x0029, /* 2118 */ 0x0028, 0x0036, 0x0029, /* 2121 */ 0x0028, 0x0037, 0x0029, /* 2124 */ 0x0028, 0x0038, 0x0029, /* 2127 */ 0x0028, 0x0039, 0x0029, /* 2130 */ 0x0028, 0x0031, 0x0030, 0x0029, /* 2134 */ 0x0028, 0x0031, 0x0031, 0x0029, /* 2138 */ 0x0028, 0x0031, 0x0032, 0x0029, /* 2142 */ 0x0028, 0x0031, 0x0033, 0x0029, /* 2146 */ 0x0028, 0x0031, 0x0034, 0x0029, /* 2150 */ 0x0028, 0x0031, 0x0035, 0x0029, /* 2154 */ 0x0028, 0x0031, 0x0036, 0x0029, /* 2158 */ 0x0028, 0x0031, 0x0037, 0x0029, /* 2162 */ 0x0028, 0x0031, 0x0038, 0x0029, /* 2166 */ 0x0028, 0x0031, 0x0039, 0x0029, /* 2170 */ 0x0028, 0x0032, 0x0030, 0x0029, /* 2174 */ 0x0031, 0x002E, /* 2176 */ 0x0032, 0x002E, /* 2178 */ 0x0033, 0x002E, /* 2180 */ 0x0034, 0x002E, /* 2182 */ 0x0035, 0x002E, /* 2184 */ 0x0036, 0x002E, /* 2186 */ 0x0037, 0x002E, /* 2188 */ 0x0038, 0x002E, /* 2190 */ 0x0039, 0x002E, /* 2192 */ 0x0031, 0x0030, 0x002E, /* 2195 */ 0x0031, 0x0031, 0x002E, /* 2198 */ 0x0031, 0x0032, 0x002E, /* 2201 */ 0x0031, 0x0033, 0x002E, /* 2204 */ 0x0031, 0x0034, 0x002E, /* 2207 */ 0x0031, 0x0035, 0x002E, /* 2210 */ 0x0031, 0x0036, 0x002E, /* 2213 */ 0x0031, 0x0037, 0x002E, /* 2216 */ 0x0031, 0x0038, 0x002E, /* 2219 */ 0x0031, 0x0039, 0x002E, /* 2222 */ 0x0032, 0x0030, 0x002E, /* 2225 */ 0x0028, 0x0061, 0x0029, /* 2228 */ 0x0028, 0x0062, 0x0029, /* 2231 */ 0x0028, 0x0063, 0x0029, /* 2234 */ 0x0028, 0x0064, 0x0029, /* 2237 */ 0x0028, 0x0065, 0x0029, /* 2240 */ 0x0028, 0x0066, 0x0029, /* 2243 */ 0x0028, 0x0067, 0x0029, /* 2246 */ 0x0028, 0x0068, 0x0029, /* 2249 */ 0x0028, 0x0069, 0x0029, /* 2252 */ 0x0028, 0x006A, 0x0029, /* 2255 */ 0x0028, 0x006B, 0x0029, /* 2258 */ 0x0028, 0x006C, 0x0029, /* 2261 */ 0x0028, 0x006D, 0x0029, /* 2264 */ 0x0028, 0x006E, 0x0029, /* 2267 */ 0x0028, 0x006F, 0x0029, /* 2270 */ 0x0028, 0x0070, 0x0029, /* 2273 */ 0x0028, 0x0071, 0x0029, /* 2276 */ 0x0028, 0x0072, 0x0029, /* 2279 */ 0x0028, 0x0073, 0x0029, /* 2282 */ 0x0028, 0x0074, 0x0029, /* 2285 */ 0x0028, 0x0075, 0x0029, /* 2288 */ 0x0028, 0x0076, 0x0029, /* 2291 */ 0x0028, 0x0077, 0x0029, /* 2294 */ 0x0028, 0x0078, 0x0029, /* 2297 */ 0x0028, 0x0079, 0x0029, /* 2300 */ 0x0028, 0x007A, 0x0029, /* 2303 */ 0x222B, 0x222B, 0x222B, 0x222B, /* 2307 */ 0x003A, 0x003A, 0x003D, /* 2310 */ 0x003D, 0x003D, /* 2312 */ 0x003D, 0x003D, 0x003D, /* 2315 */ 0x2ADD, 0x0338, /* 2317 */ 0x304B, 0x3099, /* 2319 */ 0x304D, 0x3099, /* 2321 */ 0x304F, 0x3099, /* 2323 */ 0x3051, 0x3099, /* 2325 */ 0x3053, 0x3099, /* 2327 */ 0x3055, 0x3099, /* 2329 */ 0x3057, 0x3099, /* 2331 */ 0x3059, 0x3099, /* 2333 */ 0x305B, 0x3099, /* 2335 */ 0x305D, 0x3099, /* 2337 */ 0x305F, 0x3099, /* 2339 */ 0x3061, 0x3099, /* 2341 */ 0x3064, 0x3099, /* 2343 */ 0x3066, 0x3099, /* 2345 */ 0x3068, 0x3099, /* 2347 */ 0x306F, 0x3099, /* 2349 */ 0x306F, 0x309A, /* 2351 */ 0x3072, 0x3099, /* 2353 */ 0x3072, 0x309A, /* 2355 */ 0x3075, 0x3099, /* 2357 */ 0x3075, 0x309A, /* 2359 */ 0x3078, 0x3099, /* 2361 */ 0x3078, 0x309A, /* 2363 */ 0x307B, 0x3099, /* 2365 */ 0x307B, 0x309A, /* 2367 */ 0x3046, 0x3099, /* 2369 */ 0x0020, 0x3099, /* 2371 */ 0x0020, 0x309A, /* 2373 */ 0x309D, 0x3099, /* 2375 */ 0x3088, 0x308A, /* 2377 */ 0x30AB, 0x3099, /* 2379 */ 0x30AD, 0x3099, /* 2381 */ 0x30AF, 0x3099, /* 2383 */ 0x30B1, 0x3099, /* 2385 */ 0x30B3, 0x3099, /* 2387 */ 0x30B5, 0x3099, /* 2389 */ 0x30B7, 0x3099, /* 2391 */ 0x30B9, 0x3099, /* 2393 */ 0x30BB, 0x3099, /* 2395 */ 0x30BD, 0x3099, /* 2397 */ 0x30BF, 0x3099, /* 2399 */ 0x30C1, 0x3099, /* 2401 */ 0x30C4, 0x3099, /* 2403 */ 0x30C6, 0x3099, /* 2405 */ 0x30C8, 0x3099, /* 2407 */ 0x30CF, 0x3099, /* 2409 */ 0x30CF, 0x309A, /* 2411 */ 0x30D2, 0x3099, /* 2413 */ 0x30D2, 0x309A, /* 2415 */ 0x30D5, 0x3099, /* 2417 */ 0x30D5, 0x309A, /* 2419 */ 0x30D8, 0x3099, /* 2421 */ 0x30D8, 0x309A, /* 2423 */ 0x30DB, 0x3099, /* 2425 */ 0x30DB, 0x309A, /* 2427 */ 0x30A6, 0x3099, /* 2429 */ 0x30EF, 0x3099, /* 2431 */ 0x30F0, 0x3099, /* 2433 */ 0x30F1, 0x3099, /* 2435 */ 0x30F2, 0x3099, /* 2437 */ 0x30FD, 0x3099, /* 2439 */ 0x30B3, 0x30C8, /* 2441 */ 0x0028, 0x1100, 0x0029, /* 2444 */ 0x0028, 0x1102, 0x0029, /* 2447 */ 0x0028, 0x1103, 0x0029, /* 2450 */ 0x0028, 0x1105, 0x0029, /* 2453 */ 0x0028, 0x1106, 0x0029, /* 2456 */ 0x0028, 0x1107, 0x0029, /* 2459 */ 0x0028, 0x1109, 0x0029, /* 2462 */ 0x0028, 0x110B, 0x0029, /* 2465 */ 0x0028, 0x110C, 0x0029, /* 2468 */ 0x0028, 0x110E, 0x0029, /* 2471 */ 0x0028, 0x110F, 0x0029, /* 2474 */ 0x0028, 0x1110, 0x0029, /* 2477 */ 0x0028, 0x1111, 0x0029, /* 2480 */ 0x0028, 0x1112, 0x0029, /* 2483 */ 0x0028, 0x1100, 0x1161, 0x0029, /* 2487 */ 0x0028, 0x1102, 0x1161, 0x0029, /* 2491 */ 0x0028, 0x1103, 0x1161, 0x0029, /* 2495 */ 0x0028, 0x1105, 0x1161, 0x0029, /* 2499 */ 0x0028, 0x1106, 0x1161, 0x0029, /* 2503 */ 0x0028, 0x1107, 0x1161, 0x0029, /* 2507 */ 0x0028, 0x1109, 0x1161, 0x0029, /* 2511 */ 0x0028, 0x110B, 0x1161, 0x0029, /* 2515 */ 0x0028, 0x110C, 0x1161, 0x0029, /* 2519 */ 0x0028, 0x110E, 0x1161, 0x0029, /* 2523 */ 0x0028, 0x110F, 0x1161, 0x0029, /* 2527 */ 0x0028, 0x1110, 0x1161, 0x0029, /* 2531 */ 0x0028, 0x1111, 0x1161, 0x0029, /* 2535 */ 0x0028, 0x1112, 0x1161, 0x0029, /* 2539 */ 0x0028, 0x110C, 0x116E, 0x0029, /* 2543 */ 0x0028, 0x110B, 0x1169, 0x110C, 0x1165, 0x11AB, 0x0029, /* 2550 */ 0x0028, 0x110B, 0x1169, 0x1112, 0x116E, 0x0029, /* 2556 */ 0x0028, 0x4E00, 0x0029, /* 2559 */ 0x0028, 0x4E8C, 0x0029, /* 2562 */ 0x0028, 0x4E09, 0x0029, /* 2565 */ 0x0028, 0x56DB, 0x0029, /* 2568 */ 0x0028, 0x4E94, 0x0029, /* 2571 */ 0x0028, 0x516D, 0x0029, /* 2574 */ 0x0028, 0x4E03, 0x0029, /* 2577 */ 0x0028, 0x516B, 0x0029, /* 2580 */ 0x0028, 0x4E5D, 0x0029, /* 2583 */ 0x0028, 0x5341, 0x0029, /* 2586 */ 0x0028, 0x6708, 0x0029, /* 2589 */ 0x0028, 0x706B, 0x0029, /* 2592 */ 0x0028, 0x6C34, 0x0029, /* 2595 */ 0x0028, 0x6728, 0x0029, /* 2598 */ 0x0028, 0x91D1, 0x0029, /* 2601 */ 0x0028, 0x571F, 0x0029, /* 2604 */ 0x0028, 0x65E5, 0x0029, /* 2607 */ 0x0028, 0x682A, 0x0029, /* 2610 */ 0x0028, 0x6709, 0x0029, /* 2613 */ 0x0028, 0x793E, 0x0029, /* 2616 */ 0x0028, 0x540D, 0x0029, /* 2619 */ 0x0028, 0x7279, 0x0029, /* 2622 */ 0x0028, 0x8CA1, 0x0029, /* 2625 */ 0x0028, 0x795D, 0x0029, /* 2628 */ 0x0028, 0x52B4, 0x0029, /* 2631 */ 0x0028, 0x4EE3, 0x0029, /* 2634 */ 0x0028, 0x547C, 0x0029, /* 2637 */ 0x0028, 0x5B66, 0x0029, /* 2640 */ 0x0028, 0x76E3, 0x0029, /* 2643 */ 0x0028, 0x4F01, 0x0029, /* 2646 */ 0x0028, 0x8CC7, 0x0029, /* 2649 */ 0x0028, 0x5354, 0x0029, /* 2652 */ 0x0028, 0x796D, 0x0029, /* 2655 */ 0x0028, 0x4F11, 0x0029, /* 2658 */ 0x0028, 0x81EA, 0x0029, /* 2661 */ 0x0028, 0x81F3, 0x0029, /* 2664 */ 0x0050, 0x0054, 0x0045, /* 2667 */ 0x0032, 0x0031, /* 2669 */ 0x0032, 0x0032, /* 2671 */ 0x0032, 0x0033, /* 2673 */ 0x0032, 0x0034, /* 2675 */ 0x0032, 0x0035, /* 2677 */ 0x0032, 0x0036, /* 2679 */ 0x0032, 0x0037, /* 2681 */ 0x0032, 0x0038, /* 2683 */ 0x0032, 0x0039, /* 2685 */ 0x0033, 0x0030, /* 2687 */ 0x0033, 0x0031, /* 2689 */ 0x0033, 0x0032, /* 2691 */ 0x0033, 0x0033, /* 2693 */ 0x0033, 0x0034, /* 2695 */ 0x0033, 0x0035, /* 2697 */ 0x1100, 0x1161, /* 2699 */ 0x1102, 0x1161, /* 2701 */ 0x1103, 0x1161, /* 2703 */ 0x1105, 0x1161, /* 2705 */ 0x1106, 0x1161, /* 2707 */ 0x1107, 0x1161, /* 2709 */ 0x1109, 0x1161, /* 2711 */ 0x110B, 0x1161, /* 2713 */ 0x110C, 0x1161, /* 2715 */ 0x110E, 0x1161, /* 2717 */ 0x110F, 0x1161, /* 2719 */ 0x1110, 0x1161, /* 2721 */ 0x1111, 0x1161, /* 2723 */ 0x1112, 0x1161, /* 2725 */ 0x110E, 0x1161, 0x11B7, 0x1100, 0x1169, /* 2730 */ 0x110C, 0x116E, 0x110B, 0x1174, /* 2734 */ 0x110B, 0x116E, /* 2736 */ 0x0033, 0x0036, /* 2738 */ 0x0033, 0x0037, /* 2740 */ 0x0033, 0x0038, /* 2742 */ 0x0033, 0x0039, /* 2744 */ 0x0034, 0x0030, /* 2746 */ 0x0034, 0x0031, /* 2748 */ 0x0034, 0x0032, /* 2750 */ 0x0034, 0x0033, /* 2752 */ 0x0034, 0x0034, /* 2754 */ 0x0034, 0x0035, /* 2756 */ 0x0034, 0x0036, /* 2758 */ 0x0034, 0x0037, /* 2760 */ 0x0034, 0x0038, /* 2762 */ 0x0034, 0x0039, /* 2764 */ 0x0035, 0x0030, /* 2766 */ 0x0031, 0x6708, /* 2768 */ 0x0032, 0x6708, /* 2770 */ 0x0033, 0x6708, /* 2772 */ 0x0034, 0x6708, /* 2774 */ 0x0035, 0x6708, /* 2776 */ 0x0036, 0x6708, /* 2778 */ 0x0037, 0x6708, /* 2780 */ 0x0038, 0x6708, /* 2782 */ 0x0039, 0x6708, /* 2784 */ 0x0031, 0x0030, 0x6708, /* 2787 */ 0x0031, 0x0031, 0x6708, /* 2790 */ 0x0031, 0x0032, 0x6708, /* 2793 */ 0x0048, 0x0067, /* 2795 */ 0x0065, 0x0072, 0x0067, /* 2798 */ 0x0065, 0x0056, /* 2800 */ 0x004C, 0x0054, 0x0044, /* 2803 */ 0x4EE4, 0x548C, /* 2805 */ 0x30A2, 0x30D1, 0x30FC, 0x30C8, /* 2809 */ 0x30A2, 0x30EB, 0x30D5, 0x30A1, /* 2813 */ 0x30A2, 0x30F3, 0x30DA, 0x30A2, /* 2817 */ 0x30A2, 0x30FC, 0x30EB, /* 2820 */ 0x30A4, 0x30CB, 0x30F3, 0x30B0, /* 2824 */ 0x30A4, 0x30F3, 0x30C1, /* 2827 */ 0x30A6, 0x30A9, 0x30F3, /* 2830 */ 0x30A8, 0x30B9, 0x30AF, 0x30FC, 0x30C9, /* 2835 */ 0x30A8, 0x30FC, 0x30AB, 0x30FC, /* 2839 */ 0x30AA, 0x30F3, 0x30B9, /* 2842 */ 0x30AA, 0x30FC, 0x30E0, /* 2845 */ 0x30AB, 0x30A4, 0x30EA, /* 2848 */ 0x30AB, 0x30E9, 0x30C3, 0x30C8, /* 2852 */ 0x30AB, 0x30ED, 0x30EA, 0x30FC, /* 2856 */ 0x30AC, 0x30ED, 0x30F3, /* 2859 */ 0x30AC, 0x30F3, 0x30DE, /* 2862 */ 0x30AE, 0x30AC, /* 2864 */ 0x30AE, 0x30CB, 0x30FC, /* 2867 */ 0x30AD, 0x30E5, 0x30EA, 0x30FC, /* 2871 */ 0x30AE, 0x30EB, 0x30C0, 0x30FC, /* 2875 */ 0x30AD, 0x30ED, /* 2877 */ 0x30AD, 0x30ED, 0x30B0, 0x30E9, 0x30E0, /* 2882 */ 0x30AD, 0x30ED, 0x30E1, 0x30FC, 0x30C8, 0x30EB, /* 2888 */ 0x30AD, 0x30ED, 0x30EF, 0x30C3, 0x30C8, /* 2893 */ 0x30B0, 0x30E9, 0x30E0, /* 2896 */ 0x30B0, 0x30E9, 0x30E0, 0x30C8, 0x30F3, /* 2901 */ 0x30AF, 0x30EB, 0x30BC, 0x30A4, 0x30ED, /* 2906 */ 0x30AF, 0x30ED, 0x30FC, 0x30CD, /* 2910 */ 0x30B1, 0x30FC, 0x30B9, /* 2913 */ 0x30B3, 0x30EB, 0x30CA, /* 2916 */ 0x30B3, 0x30FC, 0x30DD, /* 2919 */ 0x30B5, 0x30A4, 0x30AF, 0x30EB, /* 2923 */ 0x30B5, 0x30F3, 0x30C1, 0x30FC, 0x30E0, /* 2928 */ 0x30B7, 0x30EA, 0x30F3, 0x30B0, /* 2932 */ 0x30BB, 0x30F3, 0x30C1, /* 2935 */ 0x30BB, 0x30F3, 0x30C8, /* 2938 */ 0x30C0, 0x30FC, 0x30B9, /* 2941 */ 0x30C7, 0x30B7, /* 2943 */ 0x30C9, 0x30EB, /* 2945 */ 0x30C8, 0x30F3, /* 2947 */ 0x30CA, 0x30CE, /* 2949 */ 0x30CE, 0x30C3, 0x30C8, /* 2952 */ 0x30CF, 0x30A4, 0x30C4, /* 2955 */ 0x30D1, 0x30FC, 0x30BB, 0x30F3, 0x30C8, /* 2960 */ 0x30D1, 0x30FC, 0x30C4, /* 2963 */ 0x30D0, 0x30FC, 0x30EC, 0x30EB, /* 2967 */ 0x30D4, 0x30A2, 0x30B9, 0x30C8, 0x30EB, /* 2972 */ 0x30D4, 0x30AF, 0x30EB, /* 2975 */ 0x30D4, 0x30B3, /* 2977 */ 0x30D3, 0x30EB, /* 2979 */ 0x30D5, 0x30A1, 0x30E9, 0x30C3, 0x30C9, /* 2984 */ 0x30D5, 0x30A3, 0x30FC, 0x30C8, /* 2988 */ 0x30D6, 0x30C3, 0x30B7, 0x30A7, 0x30EB, /* 2993 */ 0x30D5, 0x30E9, 0x30F3, /* 2996 */ 0x30D8, 0x30AF, 0x30BF, 0x30FC, 0x30EB, /* 3001 */ 0x30DA, 0x30BD, /* 3003 */ 0x30DA, 0x30CB, 0x30D2, /* 3006 */ 0x30D8, 0x30EB, 0x30C4, /* 3009 */ 0x30DA, 0x30F3, 0x30B9, /* 3012 */ 0x30DA, 0x30FC, 0x30B8, /* 3015 */ 0x30D9, 0x30FC, 0x30BF, /* 3018 */ 0x30DD, 0x30A4, 0x30F3, 0x30C8, /* 3022 */ 0x30DC, 0x30EB, 0x30C8, /* 3025 */ 0x30DB, 0x30F3, /* 3027 */ 0x30DD, 0x30F3, 0x30C9, /* 3030 */ 0x30DB, 0x30FC, 0x30EB, /* 3033 */ 0x30DB, 0x30FC, 0x30F3, /* 3036 */ 0x30DE, 0x30A4, 0x30AF, 0x30ED, /* 3040 */ 0x30DE, 0x30A4, 0x30EB, /* 3043 */ 0x30DE, 0x30C3, 0x30CF, /* 3046 */ 0x30DE, 0x30EB, 0x30AF, /* 3049 */ 0x30DE, 0x30F3, 0x30B7, 0x30E7, 0x30F3, /* 3054 */ 0x30DF, 0x30AF, 0x30ED, 0x30F3, /* 3058 */ 0x30DF, 0x30EA, /* 3060 */ 0x30DF, 0x30EA, 0x30D0, 0x30FC, 0x30EB, /* 3065 */ 0x30E1, 0x30AC, /* 3067 */ 0x30E1, 0x30AC, 0x30C8, 0x30F3, /* 3071 */ 0x30E1, 0x30FC, 0x30C8, 0x30EB, /* 3075 */ 0x30E4, 0x30FC, 0x30C9, /* 3078 */ 0x30E4, 0x30FC, 0x30EB, /* 3081 */ 0x30E6, 0x30A2, 0x30F3, /* 3084 */ 0x30EA, 0x30C3, 0x30C8, 0x30EB, /* 3088 */ 0x30EA, 0x30E9, /* 3090 */ 0x30EB, 0x30D4, 0x30FC, /* 3093 */ 0x30EB, 0x30FC, 0x30D6, 0x30EB, /* 3097 */ 0x30EC, 0x30E0, /* 3099 */ 0x30EC, 0x30F3, 0x30C8, 0x30B2, 0x30F3, /* 3104 */ 0x30EF, 0x30C3, 0x30C8, /* 3107 */ 0x0030, 0x70B9, /* 3109 */ 0x0031, 0x70B9, /* 3111 */ 0x0032, 0x70B9, /* 3113 */ 0x0033, 0x70B9, /* 3115 */ 0x0034, 0x70B9, /* 3117 */ 0x0035, 0x70B9, /* 3119 */ 0x0036, 0x70B9, /* 3121 */ 0x0037, 0x70B9, /* 3123 */ 0x0038, 0x70B9, /* 3125 */ 0x0039, 0x70B9, /* 3127 */ 0x0031, 0x0030, 0x70B9, /* 3130 */ 0x0031, 0x0031, 0x70B9, /* 3133 */ 0x0031, 0x0032, 0x70B9, /* 3136 */ 0x0031, 0x0033, 0x70B9, /* 3139 */ 0x0031, 0x0034, 0x70B9, /* 3142 */ 0x0031, 0x0035, 0x70B9, /* 3145 */ 0x0031, 0x0036, 0x70B9, /* 3148 */ 0x0031, 0x0037, 0x70B9, /* 3151 */ 0x0031, 0x0038, 0x70B9, /* 3154 */ 0x0031, 0x0039, 0x70B9, /* 3157 */ 0x0032, 0x0030, 0x70B9, /* 3160 */ 0x0032, 0x0031, 0x70B9, /* 3163 */ 0x0032, 0x0032, 0x70B9, /* 3166 */ 0x0032, 0x0033, 0x70B9, /* 3169 */ 0x0032, 0x0034, 0x70B9, /* 3172 */ 0x0068, 0x0050, 0x0061, /* 3175 */ 0x0064, 0x0061, /* 3177 */ 0x0041, 0x0055, /* 3179 */ 0x0062, 0x0061, 0x0072, /* 3182 */ 0x006F, 0x0056, /* 3184 */ 0x0070, 0x0063, /* 3186 */ 0x0064, 0x006D, /* 3188 */ 0x0064, 0x006D, 0x00B2, /* 3191 */ 0x0064, 0x006D, 0x00B3, /* 3194 */ 0x0049, 0x0055, /* 3196 */ 0x5E73, 0x6210, /* 3198 */ 0x662D, 0x548C, /* 3200 */ 0x5927, 0x6B63, /* 3202 */ 0x660E, 0x6CBB, /* 3204 */ 0x682A, 0x5F0F, 0x4F1A, 0x793E, /* 3208 */ 0x0070, 0x0041, /* 3210 */ 0x006E, 0x0041, /* 3212 */ 0x03BC, 0x0041, /* 3214 */ 0x006D, 0x0041, /* 3216 */ 0x006B, 0x0041, /* 3218 */ 0x004B, 0x0042, /* 3220 */ 0x004D, 0x0042, /* 3222 */ 0x0047, 0x0042, /* 3224 */ 0x0063, 0x0061, 0x006C, /* 3227 */ 0x006B, 0x0063, 0x0061, 0x006C, /* 3231 */ 0x0070, 0x0046, /* 3233 */ 0x006E, 0x0046, /* 3235 */ 0x03BC, 0x0046, /* 3237 */ 0x03BC, 0x0067, /* 3239 */ 0x006D, 0x0067, /* 3241 */ 0x006B, 0x0067, /* 3243 */ 0x0048, 0x007A, /* 3245 */ 0x006B, 0x0048, 0x007A, /* 3248 */ 0x004D, 0x0048, 0x007A, /* 3251 */ 0x0047, 0x0048, 0x007A, /* 3254 */ 0x0054, 0x0048, 0x007A, /* 3257 */ 0x03BC, 0x2113, /* 3259 */ 0x006D, 0x2113, /* 3261 */ 0x0064, 0x2113, /* 3263 */ 0x006B, 0x2113, /* 3265 */ 0x0066, 0x006D, /* 3267 */ 0x006E, 0x006D, /* 3269 */ 0x03BC, 0x006D, /* 3271 */ 0x006D, 0x006D, /* 3273 */ 0x0063, 0x006D, /* 3275 */ 0x006B, 0x006D, /* 3277 */ 0x006D, 0x006D, 0x00B2, /* 3280 */ 0x0063, 0x006D, 0x00B2, /* 3283 */ 0x006D, 0x00B2, /* 3285 */ 0x006B, 0x006D, 0x00B2, /* 3288 */ 0x006D, 0x006D, 0x00B3, /* 3291 */ 0x0063, 0x006D, 0x00B3, /* 3294 */ 0x006D, 0x00B3, /* 3296 */ 0x006B, 0x006D, 0x00B3, /* 3299 */ 0x006D, 0x2215, 0x0073, /* 3302 */ 0x006D, 0x2215, 0x0073, 0x00B2, /* 3306 */ 0x0050, 0x0061, /* 3308 */ 0x006B, 0x0050, 0x0061, /* 3311 */ 0x004D, 0x0050, 0x0061, /* 3314 */ 0x0047, 0x0050, 0x0061, /* 3317 */ 0x0072, 0x0061, 0x0064, /* 3320 */ 0x0072, 0x0061, 0x0064, 0x2215, 0x0073, /* 3325 */ 0x0072, 0x0061, 0x0064, 0x2215, 0x0073, 0x00B2, /* 3331 */ 0x0070, 0x0073, /* 3333 */ 0x006E, 0x0073, /* 3335 */ 0x03BC, 0x0073, /* 3337 */ 0x006D, 0x0073, /* 3339 */ 0x0070, 0x0056, /* 3341 */ 0x006E, 0x0056, /* 3343 */ 0x03BC, 0x0056, /* 3345 */ 0x006D, 0x0056, /* 3347 */ 0x006B, 0x0056, /* 3349 */ 0x004D, 0x0056, /* 3351 */ 0x0070, 0x0057, /* 3353 */ 0x006E, 0x0057, /* 3355 */ 0x03BC, 0x0057, /* 3357 */ 0x006D, 0x0057, /* 3359 */ 0x006B, 0x0057, /* 3361 */ 0x004D, 0x0057, /* 3363 */ 0x006B, 0x03A9, /* 3365 */ 0x004D, 0x03A9, /* 3367 */ 0x0061, 0x002E, 0x006D, 0x002E, /* 3371 */ 0x0042, 0x0071, /* 3373 */ 0x0063, 0x0063, /* 3375 */ 0x0063, 0x0064, /* 3377 */ 0x0043, 0x2215, 0x006B, 0x0067, /* 3381 */ 0x0043, 0x006F, 0x002E, /* 3384 */ 0x0064, 0x0042, /* 3386 */ 0x0047, 0x0079, /* 3388 */ 0x0068, 0x0061, /* 3390 */ 0x0048, 0x0050, /* 3392 */ 0x0069, 0x006E, /* 3394 */ 0x004B, 0x004B, /* 3396 */ 0x004B, 0x004D, /* 3398 */ 0x006B, 0x0074, /* 3400 */ 0x006C, 0x006D, /* 3402 */ 0x006C, 0x006E, /* 3404 */ 0x006C, 0x006F, 0x0067, /* 3407 */ 0x006C, 0x0078, /* 3409 */ 0x006D, 0x0062, /* 3411 */ 0x006D, 0x0069, 0x006C, /* 3414 */ 0x006D, 0x006F, 0x006C, /* 3417 */ 0x0050, 0x0048, /* 3419 */ 0x0070, 0x002E, 0x006D, 0x002E, /* 3423 */ 0x0050, 0x0050, 0x004D, /* 3426 */ 0x0050, 0x0052, /* 3428 */ 0x0073, 0x0072, /* 3430 */ 0x0053, 0x0076, /* 3432 */ 0x0057, 0x0062, /* 3434 */ 0x0056, 0x2215, 0x006D, /* 3437 */ 0x0041, 0x2215, 0x006D, /* 3440 */ 0x0031, 0x65E5, /* 3442 */ 0x0032, 0x65E5, /* 3444 */ 0x0033, 0x65E5, /* 3446 */ 0x0034, 0x65E5, /* 3448 */ 0x0035, 0x65E5, /* 3450 */ 0x0036, 0x65E5, /* 3452 */ 0x0037, 0x65E5, /* 3454 */ 0x0038, 0x65E5, /* 3456 */ 0x0039, 0x65E5, /* 3458 */ 0x0031, 0x0030, 0x65E5, /* 3461 */ 0x0031, 0x0031, 0x65E5, /* 3464 */ 0x0031, 0x0032, 0x65E5, /* 3467 */ 0x0031, 0x0033, 0x65E5, /* 3470 */ 0x0031, 0x0034, 0x65E5, /* 3473 */ 0x0031, 0x0035, 0x65E5, /* 3476 */ 0x0031, 0x0036, 0x65E5, /* 3479 */ 0x0031, 0x0037, 0x65E5, /* 3482 */ 0x0031, 0x0038, 0x65E5, /* 3485 */ 0x0031, 0x0039, 0x65E5, /* 3488 */ 0x0032, 0x0030, 0x65E5, /* 3491 */ 0x0032, 0x0031, 0x65E5, /* 3494 */ 0x0032, 0x0032, 0x65E5, /* 3497 */ 0x0032, 0x0033, 0x65E5, /* 3500 */ 0x0032, 0x0034, 0x65E5, /* 3503 */ 0x0032, 0x0035, 0x65E5, /* 3506 */ 0x0032, 0x0036, 0x65E5, /* 3509 */ 0x0032, 0x0037, 0x65E5, /* 3512 */ 0x0032, 0x0038, 0x65E5, /* 3515 */ 0x0032, 0x0039, 0x65E5, /* 3518 */ 0x0033, 0x0030, 0x65E5, /* 3521 */ 0x0033, 0x0031, 0x65E5, /* 3524 */ 0x0067, 0x0061, 0x006C, /* 3527 */ 0x242EE, /* 3528 */ 0x2284A, /* 3529 */ 0x22844, /* 3530 */ 0x233D5, /* 3531 */ 0x25249, /* 3532 */ 0x25CD0, /* 3533 */ 0x27ED3, /* 3534 */ 0x0066, 0x0066, /* 3536 */ 0x0066, 0x0069, /* 3538 */ 0x0066, 0x006C, /* 3540 */ 0x0066, 0x0066, 0x0069, /* 3543 */ 0x0066, 0x0066, 0x006C, /* 3546 */ 0x017F, 0x0074, /* 3548 */ 0x0073, 0x0074, /* 3550 */ 0x0574, 0x0576, /* 3552 */ 0x0574, 0x0565, /* 3554 */ 0x0574, 0x056B, /* 3556 */ 0x057E, 0x0576, /* 3558 */ 0x0574, 0x056D, /* 3560 */ 0x05D9, 0x05B4, /* 3562 */ 0x05F2, 0x05B7, /* 3564 */ 0x05E9, 0x05C1, /* 3566 */ 0x05E9, 0x05C2, /* 3568 */ 0xFB49, 0x05C1, /* 3570 */ 0xFB49, 0x05C2, /* 3572 */ 0x05D0, 0x05B7, /* 3574 */ 0x05D0, 0x05B8, /* 3576 */ 0x05D0, 0x05BC, /* 3578 */ 0x05D1, 0x05BC, /* 3580 */ 0x05D2, 0x05BC, /* 3582 */ 0x05D3, 0x05BC, /* 3584 */ 0x05D4, 0x05BC, /* 3586 */ 0x05D5, 0x05BC, /* 3588 */ 0x05D6, 0x05BC, /* 3590 */ 0x05D8, 0x05BC, /* 3592 */ 0x05D9, 0x05BC, /* 3594 */ 0x05DA, 0x05BC, /* 3596 */ 0x05DB, 0x05BC, /* 3598 */ 0x05DC, 0x05BC, /* 3600 */ 0x05DE, 0x05BC, /* 3602 */ 0x05E0, 0x05BC, /* 3604 */ 0x05E1, 0x05BC, /* 3606 */ 0x05E3, 0x05BC, /* 3608 */ 0x05E4, 0x05BC, /* 3610 */ 0x05E6, 0x05BC, /* 3612 */ 0x05E7, 0x05BC, /* 3614 */ 0x05E8, 0x05BC, /* 3616 */ 0x05E9, 0x05BC, /* 3618 */ 0x05EA, 0x05BC, /* 3620 */ 0x05D5, 0x05B9, /* 3622 */ 0x05D1, 0x05BF, /* 3624 */ 0x05DB, 0x05BF, /* 3626 */ 0x05E4, 0x05BF, /* 3628 */ 0x05D0, 0x05DC, /* 3630 */ 0x0626, 0x0627, /* 3632 */ 0x0626, 0x0627, /* 3634 */ 0x0626, 0x06D5, /* 3636 */ 0x0626, 0x06D5, /* 3638 */ 0x0626, 0x0648, /* 3640 */ 0x0626, 0x0648, /* 3642 */ 0x0626, 0x06C7, /* 3644 */ 0x0626, 0x06C7, /* 3646 */ 0x0626, 0x06C6, /* 3648 */ 0x0626, 0x06C6, /* 3650 */ 0x0626, 0x06C8, /* 3652 */ 0x0626, 0x06C8, /* 3654 */ 0x0626, 0x06D0, /* 3656 */ 0x0626, 0x06D0, /* 3658 */ 0x0626, 0x06D0, /* 3660 */ 0x0626, 0x0649, /* 3662 */ 0x0626, 0x0649, /* 3664 */ 0x0626, 0x0649, /* 3666 */ 0x0626, 0x062C, /* 3668 */ 0x0626, 0x062D, /* 3670 */ 0x0626, 0x0645, /* 3672 */ 0x0626, 0x0649, /* 3674 */ 0x0626, 0x064A, /* 3676 */ 0x0628, 0x062C, /* 3678 */ 0x0628, 0x062D, /* 3680 */ 0x0628, 0x062E, /* 3682 */ 0x0628, 0x0645, /* 3684 */ 0x0628, 0x0649, /* 3686 */ 0x0628, 0x064A, /* 3688 */ 0x062A, 0x062C, /* 3690 */ 0x062A, 0x062D, /* 3692 */ 0x062A, 0x062E, /* 3694 */ 0x062A, 0x0645, /* 3696 */ 0x062A, 0x0649, /* 3698 */ 0x062A, 0x064A, /* 3700 */ 0x062B, 0x062C, /* 3702 */ 0x062B, 0x0645, /* 3704 */ 0x062B, 0x0649, /* 3706 */ 0x062B, 0x064A, /* 3708 */ 0x062C, 0x062D, /* 3710 */ 0x062C, 0x0645, /* 3712 */ 0x062D, 0x062C, /* 3714 */ 0x062D, 0x0645, /* 3716 */ 0x062E, 0x062C, /* 3718 */ 0x062E, 0x062D, /* 3720 */ 0x062E, 0x0645, /* 3722 */ 0x0633, 0x062C, /* 3724 */ 0x0633, 0x062D, /* 3726 */ 0x0633, 0x062E, /* 3728 */ 0x0633, 0x0645, /* 3730 */ 0x0635, 0x062D, /* 3732 */ 0x0635, 0x0645, /* 3734 */ 0x0636, 0x062C, /* 3736 */ 0x0636, 0x062D, /* 3738 */ 0x0636, 0x062E, /* 3740 */ 0x0636, 0x0645, /* 3742 */ 0x0637, 0x062D, /* 3744 */ 0x0637, 0x0645, /* 3746 */ 0x0638, 0x0645, /* 3748 */ 0x0639, 0x062C, /* 3750 */ 0x0639, 0x0645, /* 3752 */ 0x063A, 0x062C, /* 3754 */ 0x063A, 0x0645, /* 3756 */ 0x0641, 0x062C, /* 3758 */ 0x0641, 0x062D, /* 3760 */ 0x0641, 0x062E, /* 3762 */ 0x0641, 0x0645, /* 3764 */ 0x0641, 0x0649, /* 3766 */ 0x0641, 0x064A, /* 3768 */ 0x0642, 0x062D, /* 3770 */ 0x0642, 0x0645, /* 3772 */ 0x0642, 0x0649, /* 3774 */ 0x0642, 0x064A, /* 3776 */ 0x0643, 0x0627, /* 3778 */ 0x0643, 0x062C, /* 3780 */ 0x0643, 0x062D, /* 3782 */ 0x0643, 0x062E, /* 3784 */ 0x0643, 0x0644, /* 3786 */ 0x0643, 0x0645, /* 3788 */ 0x0643, 0x0649, /* 3790 */ 0x0643, 0x064A, /* 3792 */ 0x0644, 0x062C, /* 3794 */ 0x0644, 0x062D, /* 3796 */ 0x0644, 0x062E, /* 3798 */ 0x0644, 0x0645, /* 3800 */ 0x0644, 0x0649, /* 3802 */ 0x0644, 0x064A, /* 3804 */ 0x0645, 0x062C, /* 3806 */ 0x0645, 0x062D, /* 3808 */ 0x0645, 0x062E, /* 3810 */ 0x0645, 0x0645, /* 3812 */ 0x0645, 0x0649, /* 3814 */ 0x0645, 0x064A, /* 3816 */ 0x0646, 0x062C, /* 3818 */ 0x0646, 0x062D, /* 3820 */ 0x0646, 0x062E, /* 3822 */ 0x0646, 0x0645, /* 3824 */ 0x0646, 0x0649, /* 3826 */ 0x0646, 0x064A, /* 3828 */ 0x0647, 0x062C, /* 3830 */ 0x0647, 0x0645, /* 3832 */ 0x0647, 0x0649, /* 3834 */ 0x0647, 0x064A, /* 3836 */ 0x064A, 0x062C, /* 3838 */ 0x064A, 0x062D, /* 3840 */ 0x064A, 0x062E, /* 3842 */ 0x064A, 0x0645, /* 3844 */ 0x064A, 0x0649, /* 3846 */ 0x064A, 0x064A, /* 3848 */ 0x0630, 0x0670, /* 3850 */ 0x0631, 0x0670, /* 3852 */ 0x0649, 0x0670, /* 3854 */ 0x0020, 0x064C, 0x0651, /* 3857 */ 0x0020, 0x064D, 0x0651, /* 3860 */ 0x0020, 0x064E, 0x0651, /* 3863 */ 0x0020, 0x064F, 0x0651, /* 3866 */ 0x0020, 0x0650, 0x0651, /* 3869 */ 0x0020, 0x0651, 0x0670, /* 3872 */ 0x0626, 0x0631, /* 3874 */ 0x0626, 0x0632, /* 3876 */ 0x0626, 0x0645, /* 3878 */ 0x0626, 0x0646, /* 3880 */ 0x0626, 0x0649, /* 3882 */ 0x0626, 0x064A, /* 3884 */ 0x0628, 0x0631, /* 3886 */ 0x0628, 0x0632, /* 3888 */ 0x0628, 0x0645, /* 3890 */ 0x0628, 0x0646, /* 3892 */ 0x0628, 0x0649, /* 3894 */ 0x0628, 0x064A, /* 3896 */ 0x062A, 0x0631, /* 3898 */ 0x062A, 0x0632, /* 3900 */ 0x062A, 0x0645, /* 3902 */ 0x062A, 0x0646, /* 3904 */ 0x062A, 0x0649, /* 3906 */ 0x062A, 0x064A, /* 3908 */ 0x062B, 0x0631, /* 3910 */ 0x062B, 0x0632, /* 3912 */ 0x062B, 0x0645, /* 3914 */ 0x062B, 0x0646, /* 3916 */ 0x062B, 0x0649, /* 3918 */ 0x062B, 0x064A, /* 3920 */ 0x0641, 0x0649, /* 3922 */ 0x0641, 0x064A, /* 3924 */ 0x0642, 0x0649, /* 3926 */ 0x0642, 0x064A, /* 3928 */ 0x0643, 0x0627, /* 3930 */ 0x0643, 0x0644, /* 3932 */ 0x0643, 0x0645, /* 3934 */ 0x0643, 0x0649, /* 3936 */ 0x0643, 0x064A, /* 3938 */ 0x0644, 0x0645, /* 3940 */ 0x0644, 0x0649, /* 3942 */ 0x0644, 0x064A, /* 3944 */ 0x0645, 0x0627, /* 3946 */ 0x0645, 0x0645, /* 3948 */ 0x0646, 0x0631, /* 3950 */ 0x0646, 0x0632, /* 3952 */ 0x0646, 0x0645, /* 3954 */ 0x0646, 0x0646, /* 3956 */ 0x0646, 0x0649, /* 3958 */ 0x0646, 0x064A, /* 3960 */ 0x0649, 0x0670, /* 3962 */ 0x064A, 0x0631, /* 3964 */ 0x064A, 0x0632, /* 3966 */ 0x064A, 0x0645, /* 3968 */ 0x064A, 0x0646, /* 3970 */ 0x064A, 0x0649, /* 3972 */ 0x064A, 0x064A, /* 3974 */ 0x0626, 0x062C, /* 3976 */ 0x0626, 0x062D, /* 3978 */ 0x0626, 0x062E, /* 3980 */ 0x0626, 0x0645, /* 3982 */ 0x0626, 0x0647, /* 3984 */ 0x0628, 0x062C, /* 3986 */ 0x0628, 0x062D, /* 3988 */ 0x0628, 0x062E, /* 3990 */ 0x0628, 0x0645, /* 3992 */ 0x0628, 0x0647, /* 3994 */ 0x062A, 0x062C, /* 3996 */ 0x062A, 0x062D, /* 3998 */ 0x062A, 0x062E, /* 4000 */ 0x062A, 0x0645, /* 4002 */ 0x062A, 0x0647, /* 4004 */ 0x062B, 0x0645, /* 4006 */ 0x062C, 0x062D, /* 4008 */ 0x062C, 0x0645, /* 4010 */ 0x062D, 0x062C, /* 4012 */ 0x062D, 0x0645, /* 4014 */ 0x062E, 0x062C, /* 4016 */ 0x062E, 0x0645, /* 4018 */ 0x0633, 0x062C, /* 4020 */ 0x0633, 0x062D, /* 4022 */ 0x0633, 0x062E, /* 4024 */ 0x0633, 0x0645, /* 4026 */ 0x0635, 0x062D, /* 4028 */ 0x0635, 0x062E, /* 4030 */ 0x0635, 0x0645, /* 4032 */ 0x0636, 0x062C, /* 4034 */ 0x0636, 0x062D, /* 4036 */ 0x0636, 0x062E, /* 4038 */ 0x0636, 0x0645, /* 4040 */ 0x0637, 0x062D, /* 4042 */ 0x0638, 0x0645, /* 4044 */ 0x0639, 0x062C, /* 4046 */ 0x0639, 0x0645, /* 4048 */ 0x063A, 0x062C, /* 4050 */ 0x063A, 0x0645, /* 4052 */ 0x0641, 0x062C, /* 4054 */ 0x0641, 0x062D, /* 4056 */ 0x0641, 0x062E, /* 4058 */ 0x0641, 0x0645, /* 4060 */ 0x0642, 0x062D, /* 4062 */ 0x0642, 0x0645, /* 4064 */ 0x0643, 0x062C, /* 4066 */ 0x0643, 0x062D, /* 4068 */ 0x0643, 0x062E, /* 4070 */ 0x0643, 0x0644, /* 4072 */ 0x0643, 0x0645, /* 4074 */ 0x0644, 0x062C, /* 4076 */ 0x0644, 0x062D, /* 4078 */ 0x0644, 0x062E, /* 4080 */ 0x0644, 0x0645, /* 4082 */ 0x0644, 0x0647, /* 4084 */ 0x0645, 0x062C, /* 4086 */ 0x0645, 0x062D, /* 4088 */ 0x0645, 0x062E, /* 4090 */ 0x0645, 0x0645, /* 4092 */ 0x0646, 0x062C, /* 4094 */ 0x0646, 0x062D, /* 4096 */ 0x0646, 0x062E, /* 4098 */ 0x0646, 0x0645, /* 4100 */ 0x0646, 0x0647, /* 4102 */ 0x0647, 0x062C, /* 4104 */ 0x0647, 0x0645, /* 4106 */ 0x0647, 0x0670, /* 4108 */ 0x064A, 0x062C, /* 4110 */ 0x064A, 0x062D, /* 4112 */ 0x064A, 0x062E, /* 4114 */ 0x064A, 0x0645, /* 4116 */ 0x064A, 0x0647, /* 4118 */ 0x0626, 0x0645, /* 4120 */ 0x0626, 0x0647, /* 4122 */ 0x0628, 0x0645, /* 4124 */ 0x0628, 0x0647, /* 4126 */ 0x062A, 0x0645, /* 4128 */ 0x062A, 0x0647, /* 4130 */ 0x062B, 0x0645, /* 4132 */ 0x062B, 0x0647, /* 4134 */ 0x0633, 0x0645, /* 4136 */ 0x0633, 0x0647, /* 4138 */ 0x0634, 0x0645, /* 4140 */ 0x0634, 0x0647, /* 4142 */ 0x0643, 0x0644, /* 4144 */ 0x0643, 0x0645, /* 4146 */ 0x0644, 0x0645, /* 4148 */ 0x0646, 0x0645, /* 4150 */ 0x0646, 0x0647, /* 4152 */ 0x064A, 0x0645, /* 4154 */ 0x064A, 0x0647, /* 4156 */ 0x0640, 0x064E, 0x0651, /* 4159 */ 0x0640, 0x064F, 0x0651, /* 4162 */ 0x0640, 0x0650, 0x0651, /* 4165 */ 0x0637, 0x0649, /* 4167 */ 0x0637, 0x064A, /* 4169 */ 0x0639, 0x0649, /* 4171 */ 0x0639, 0x064A, /* 4173 */ 0x063A, 0x0649, /* 4175 */ 0x063A, 0x064A, /* 4177 */ 0x0633, 0x0649, /* 4179 */ 0x0633, 0x064A, /* 4181 */ 0x0634, 0x0649, /* 4183 */ 0x0634, 0x064A, /* 4185 */ 0x062D, 0x0649, /* 4187 */ 0x062D, 0x064A, /* 4189 */ 0x062C, 0x0649, /* 4191 */ 0x062C, 0x064A, /* 4193 */ 0x062E, 0x0649, /* 4195 */ 0x062E, 0x064A, /* 4197 */ 0x0635, 0x0649, /* 4199 */ 0x0635, 0x064A, /* 4201 */ 0x0636, 0x0649, /* 4203 */ 0x0636, 0x064A, /* 4205 */ 0x0634, 0x062C, /* 4207 */ 0x0634, 0x062D, /* 4209 */ 0x0634, 0x062E, /* 4211 */ 0x0634, 0x0645, /* 4213 */ 0x0634, 0x0631, /* 4215 */ 0x0633, 0x0631, /* 4217 */ 0x0635, 0x0631, /* 4219 */ 0x0636, 0x0631, /* 4221 */ 0x0637, 0x0649, /* 4223 */ 0x0637, 0x064A, /* 4225 */ 0x0639, 0x0649, /* 4227 */ 0x0639, 0x064A, /* 4229 */ 0x063A, 0x0649, /* 4231 */ 0x063A, 0x064A, /* 4233 */ 0x0633, 0x0649, /* 4235 */ 0x0633, 0x064A, /* 4237 */ 0x0634, 0x0649, /* 4239 */ 0x0634, 0x064A, /* 4241 */ 0x062D, 0x0649, /* 4243 */ 0x062D, 0x064A, /* 4245 */ 0x062C, 0x0649, /* 4247 */ 0x062C, 0x064A, /* 4249 */ 0x062E, 0x0649, /* 4251 */ 0x062E, 0x064A, /* 4253 */ 0x0635, 0x0649, /* 4255 */ 0x0635, 0x064A, /* 4257 */ 0x0636, 0x0649, /* 4259 */ 0x0636, 0x064A, /* 4261 */ 0x0634, 0x062C, /* 4263 */ 0x0634, 0x062D, /* 4265 */ 0x0634, 0x062E, /* 4267 */ 0x0634, 0x0645, /* 4269 */ 0x0634, 0x0631, /* 4271 */ 0x0633, 0x0631, /* 4273 */ 0x0635, 0x0631, /* 4275 */ 0x0636, 0x0631, /* 4277 */ 0x0634, 0x062C, /* 4279 */ 0x0634, 0x062D, /* 4281 */ 0x0634, 0x062E, /* 4283 */ 0x0634, 0x0645, /* 4285 */ 0x0633, 0x0647, /* 4287 */ 0x0634, 0x0647, /* 4289 */ 0x0637, 0x0645, /* 4291 */ 0x0633, 0x062C, /* 4293 */ 0x0633, 0x062D, /* 4295 */ 0x0633, 0x062E, /* 4297 */ 0x0634, 0x062C, /* 4299 */ 0x0634, 0x062D, /* 4301 */ 0x0634, 0x062E, /* 4303 */ 0x0637, 0x0645, /* 4305 */ 0x0638, 0x0645, /* 4307 */ 0x0627, 0x064B, /* 4309 */ 0x0627, 0x064B, /* 4311 */ 0x062A, 0x062C, 0x0645, /* 4314 */ 0x062A, 0x062D, 0x062C, /* 4317 */ 0x062A, 0x062D, 0x062C, /* 4320 */ 0x062A, 0x062D, 0x0645, /* 4323 */ 0x062A, 0x062E, 0x0645, /* 4326 */ 0x062A, 0x0645, 0x062C, /* 4329 */ 0x062A, 0x0645, 0x062D, /* 4332 */ 0x062A, 0x0645, 0x062E, /* 4335 */ 0x062C, 0x0645, 0x062D, /* 4338 */ 0x062C, 0x0645, 0x062D, /* 4341 */ 0x062D, 0x0645, 0x064A, /* 4344 */ 0x062D, 0x0645, 0x0649, /* 4347 */ 0x0633, 0x062D, 0x062C, /* 4350 */ 0x0633, 0x062C, 0x062D, /* 4353 */ 0x0633, 0x062C, 0x0649, /* 4356 */ 0x0633, 0x0645, 0x062D, /* 4359 */ 0x0633, 0x0645, 0x062D, /* 4362 */ 0x0633, 0x0645, 0x062C, /* 4365 */ 0x0633, 0x0645, 0x0645, /* 4368 */ 0x0633, 0x0645, 0x0645, /* 4371 */ 0x0635, 0x062D, 0x062D, /* 4374 */ 0x0635, 0x062D, 0x062D, /* 4377 */ 0x0635, 0x0645, 0x0645, /* 4380 */ 0x0634, 0x062D, 0x0645, /* 4383 */ 0x0634, 0x062D, 0x0645, /* 4386 */ 0x0634, 0x062C, 0x064A, /* 4389 */ 0x0634, 0x0645, 0x062E, /* 4392 */ 0x0634, 0x0645, 0x062E, /* 4395 */ 0x0634, 0x0645, 0x0645, /* 4398 */ 0x0634, 0x0645, 0x0645, /* 4401 */ 0x0636, 0x062D, 0x0649, /* 4404 */ 0x0636, 0x062E, 0x0645, /* 4407 */ 0x0636, 0x062E, 0x0645, /* 4410 */ 0x0637, 0x0645, 0x062D, /* 4413 */ 0x0637, 0x0645, 0x062D, /* 4416 */ 0x0637, 0x0645, 0x0645, /* 4419 */ 0x0637, 0x0645, 0x064A, /* 4422 */ 0x0639, 0x062C, 0x0645, /* 4425 */ 0x0639, 0x0645, 0x0645, /* 4428 */ 0x0639, 0x0645, 0x0645, /* 4431 */ 0x0639, 0x0645, 0x0649, /* 4434 */ 0x063A, 0x0645, 0x0645, /* 4437 */ 0x063A, 0x0645, 0x064A, /* 4440 */ 0x063A, 0x0645, 0x0649, /* 4443 */ 0x0641, 0x062E, 0x0645, /* 4446 */ 0x0641, 0x062E, 0x0645, /* 4449 */ 0x0642, 0x0645, 0x062D, /* 4452 */ 0x0642, 0x0645, 0x0645, /* 4455 */ 0x0644, 0x062D, 0x0645, /* 4458 */ 0x0644, 0x062D, 0x064A, /* 4461 */ 0x0644, 0x062D, 0x0649, /* 4464 */ 0x0644, 0x062C, 0x062C, /* 4467 */ 0x0644, 0x062C, 0x062C, /* 4470 */ 0x0644, 0x062E, 0x0645, /* 4473 */ 0x0644, 0x062E, 0x0645, /* 4476 */ 0x0644, 0x0645, 0x062D, /* 4479 */ 0x0644, 0x0645, 0x062D, /* 4482 */ 0x0645, 0x062D, 0x062C, /* 4485 */ 0x0645, 0x062D, 0x0645, /* 4488 */ 0x0645, 0x062D, 0x064A, /* 4491 */ 0x0645, 0x062C, 0x062D, /* 4494 */ 0x0645, 0x062C, 0x0645, /* 4497 */ 0x0645, 0x062E, 0x062C, /* 4500 */ 0x0645, 0x062E, 0x0645, /* 4503 */ 0x0645, 0x062C, 0x062E, /* 4506 */ 0x0647, 0x0645, 0x062C, /* 4509 */ 0x0647, 0x0645, 0x0645, /* 4512 */ 0x0646, 0x062D, 0x0645, /* 4515 */ 0x0646, 0x062D, 0x0649, /* 4518 */ 0x0646, 0x062C, 0x0645, /* 4521 */ 0x0646, 0x062C, 0x0645, /* 4524 */ 0x0646, 0x062C, 0x0649, /* 4527 */ 0x0646, 0x0645, 0x064A, /* 4530 */ 0x0646, 0x0645, 0x0649, /* 4533 */ 0x064A, 0x0645, 0x0645, /* 4536 */ 0x064A, 0x0645, 0x0645, /* 4539 */ 0x0628, 0x062E, 0x064A, /* 4542 */ 0x062A, 0x062C, 0x064A, /* 4545 */ 0x062A, 0x062C, 0x0649, /* 4548 */ 0x062A, 0x062E, 0x064A, /* 4551 */ 0x062A, 0x062E, 0x0649, /* 4554 */ 0x062A, 0x0645, 0x064A, /* 4557 */ 0x062A, 0x0645, 0x0649, /* 4560 */ 0x062C, 0x0645, 0x064A, /* 4563 */ 0x062C, 0x062D, 0x0649, /* 4566 */ 0x062C, 0x0645, 0x0649, /* 4569 */ 0x0633, 0x062E, 0x0649, /* 4572 */ 0x0635, 0x062D, 0x064A, /* 4575 */ 0x0634, 0x062D, 0x064A, /* 4578 */ 0x0636, 0x062D, 0x064A, /* 4581 */ 0x0644, 0x062C, 0x064A, /* 4584 */ 0x0644, 0x0645, 0x064A, /* 4587 */ 0x064A, 0x062D, 0x064A, /* 4590 */ 0x064A, 0x062C, 0x064A, /* 4593 */ 0x064A, 0x0645, 0x064A, /* 4596 */ 0x0645, 0x0645, 0x064A, /* 4599 */ 0x0642, 0x0645, 0x064A, /* 4602 */ 0x0646, 0x062D, 0x064A, /* 4605 */ 0x0642, 0x0645, 0x062D, /* 4608 */ 0x0644, 0x062D, 0x0645, /* 4611 */ 0x0639, 0x0645, 0x064A, /* 4614 */ 0x0643, 0x0645, 0x064A, /* 4617 */ 0x0646, 0x062C, 0x062D, /* 4620 */ 0x0645, 0x062E, 0x064A, /* 4623 */ 0x0644, 0x062C, 0x0645, /* 4626 */ 0x0643, 0x0645, 0x0645, /* 4629 */ 0x0644, 0x062C, 0x0645, /* 4632 */ 0x0646, 0x062C, 0x062D, /* 4635 */ 0x062C, 0x062D, 0x064A, /* 4638 */ 0x062D, 0x062C, 0x064A, /* 4641 */ 0x0645, 0x062C, 0x064A, /* 4644 */ 0x0641, 0x0645, 0x064A, /* 4647 */ 0x0628, 0x062D, 0x064A, /* 4650 */ 0x0643, 0x0645, 0x0645, /* 4653 */ 0x0639, 0x062C, 0x0645, /* 4656 */ 0x0635, 0x0645, 0x0645, /* 4659 */ 0x0633, 0x062E, 0x064A, /* 4662 */ 0x0646, 0x062C, 0x064A, /* 4665 */ 0x0635, 0x0644, 0x06D2, /* 4668 */ 0x0642, 0x0644, 0x06D2, /* 4671 */ 0x0627, 0x0644, 0x0644, 0x0647, /* 4675 */ 0x0627, 0x0643, 0x0628, 0x0631, /* 4679 */ 0x0645, 0x062D, 0x0645, 0x062F, /* 4683 */ 0x0635, 0x0644, 0x0639, 0x0645, /* 4687 */ 0x0631, 0x0633, 0x0648, 0x0644, /* 4691 */ 0x0639, 0x0644, 0x064A, 0x0647, /* 4695 */ 0x0648, 0x0633, 0x0644, 0x0645, /* 4699 */ 0x0635, 0x0644, 0x0649, /* 4702 */ 0x0635, 0x0644, 0x0649, 0x0020, 0x0627, 0x0644, 0x0644, 0x0647, 0x0020, 0x0639, 0x0644, 0x064A, 0x0647, 0x0020, 0x0648, 0x0633, 0x0644, 0x0645, /* 4720 */ 0x062C, 0x0644, 0x0020, 0x062C, 0x0644, 0x0627, 0x0644, 0x0647, /* 4728 */ 0x0631, 0x06CC, 0x0627, 0x0644, /* 4732 */ 0x0020, 0x064B, /* 4734 */ 0x0640, 0x064B, /* 4736 */ 0x0020, 0x064C, /* 4738 */ 0x0020, 0x064D, /* 4740 */ 0x0020, 0x064E, /* 4742 */ 0x0640, 0x064E, /* 4744 */ 0x0020, 0x064F, /* 4746 */ 0x0640, 0x064F, /* 4748 */ 0x0020, 0x0650, /* 4750 */ 0x0640, 0x0650, /* 4752 */ 0x0020, 0x0651, /* 4754 */ 0x0640, 0x0651, /* 4756 */ 0x0020, 0x0652, /* 4758 */ 0x0640, 0x0652, /* 4760 */ 0x0644, 0x0622, /* 4762 */ 0x0644, 0x0622, /* 4764 */ 0x0644, 0x0623, /* 4766 */ 0x0644, 0x0623, /* 4768 */ 0x0644, 0x0625, /* 4770 */ 0x0644, 0x0625, /* 4772 */ 0x0644, 0x0627, /* 4774 */ 0x0644, 0x0627, /* 4776 */ 0x105D2, 0x0307, /* 4778 */ 0x105DA, 0x0307, /* 4780 */ 0x1DF04, /* 4781 */ 0x1DF05, /* 4782 */ 0x1DF06, /* 4783 */ 0x1DF08, /* 4784 */ 0x1DF0A, /* 4785 */ 0x1DF1E, /* 4786 */ 0x11099, 0x110BA, /* 4788 */ 0x1109B, 0x110BA, /* 4790 */ 0x110A5, 0x110BA, /* 4792 */ 0x11131, 0x11127, /* 4794 */ 0x11132, 0x11127, /* 4796 */ 0x11347, 0x1133E, /* 4798 */ 0x11347, 0x11357, /* 4800 */ 0x11382, 0x113C9, /* 4802 */ 0x11384, 0x113BB, /* 4804 */ 0x1138B, 0x113C2, /* 4806 */ 0x11390, 0x113C9, /* 4808 */ 0x113C2, 0x113C2, /* 4810 */ 0x113C2, 0x113B8, /* 4812 */ 0x113C2, 0x113C9, /* 4814 */ 0x114B9, 0x114BA, /* 4816 */ 0x114B9, 0x114B0, /* 4818 */ 0x114B9, 0x114BD, /* 4820 */ 0x115B8, 0x115AF, /* 4822 */ 0x115B9, 0x115AF, /* 4824 */ 0x11935, 0x11930, /* 4826 */ 0x1611E, 0x1611E, /* 4828 */ 0x1611E, 0x16129, /* 4830 */ 0x1611E, 0x1611F, /* 4832 */ 0x16129, 0x1611F, /* 4834 */ 0x1611E, 0x16120, /* 4836 */ 0x16121, 0x1611F, /* 4838 */ 0x16122, 0x1611F, /* 4840 */ 0x16121, 0x16120, /* 4842 */ 0x16D67, 0x16D67, /* 4844 */ 0x16D63, 0x16D67, /* 4846 */ 0x16D69, 0x16D67, /* 4848 */ 0x1D157, 0x1D165, /* 4850 */ 0x1D158, 0x1D165, /* 4852 */ 0x1D15F, 0x1D16E, /* 4854 */ 0x1D15F, 0x1D16F, /* 4856 */ 0x1D15F, 0x1D170, /* 4858 */ 0x1D15F, 0x1D171, /* 4860 */ 0x1D15F, 0x1D172, /* 4862 */ 0x1D1B9, 0x1D165, /* 4864 */ 0x1D1BA, 0x1D165, /* 4866 */ 0x1D1BB, 0x1D16E, /* 4868 */ 0x1D1BC, 0x1D16E, /* 4870 */ 0x1D1BB, 0x1D16F, /* 4872 */ 0x1D1BC, 0x1D16F, /* 4874 */ 0x0030, 0x002E, /* 4876 */ 0x0030, 0x002C, /* 4878 */ 0x0031, 0x002C, /* 4880 */ 0x0032, 0x002C, /* 4882 */ 0x0033, 0x002C, /* 4884 */ 0x0034, 0x002C, /* 4886 */ 0x0035, 0x002C, /* 4888 */ 0x0036, 0x002C, /* 4890 */ 0x0037, 0x002C, /* 4892 */ 0x0038, 0x002C, /* 4894 */ 0x0039, 0x002C, /* 4896 */ 0x0028, 0x0041, 0x0029, /* 4899 */ 0x0028, 0x0042, 0x0029, /* 4902 */ 0x0028, 0x0043, 0x0029, /* 4905 */ 0x0028, 0x0044, 0x0029, /* 4908 */ 0x0028, 0x0045, 0x0029, /* 4911 */ 0x0028, 0x0046, 0x0029, /* 4914 */ 0x0028, 0x0047, 0x0029, /* 4917 */ 0x0028, 0x0048, 0x0029, /* 4920 */ 0x0028, 0x0049, 0x0029, /* 4923 */ 0x0028, 0x004A, 0x0029, /* 4926 */ 0x0028, 0x004B, 0x0029, /* 4929 */ 0x0028, 0x004C, 0x0029, /* 4932 */ 0x0028, 0x004D, 0x0029, /* 4935 */ 0x0028, 0x004E, 0x0029, /* 4938 */ 0x0028, 0x004F, 0x0029, /* 4941 */ 0x0028, 0x0050, 0x0029, /* 4944 */ 0x0028, 0x0051, 0x0029, /* 4947 */ 0x0028, 0x0052, 0x0029, /* 4950 */ 0x0028, 0x0053, 0x0029, /* 4953 */ 0x0028, 0x0054, 0x0029, /* 4956 */ 0x0028, 0x0055, 0x0029, /* 4959 */ 0x0028, 0x0056, 0x0029, /* 4962 */ 0x0028, 0x0057, 0x0029, /* 4965 */ 0x0028, 0x0058, 0x0029, /* 4968 */ 0x0028, 0x0059, 0x0029, /* 4971 */ 0x0028, 0x005A, 0x0029, /* 4974 */ 0x3014, 0x0053, 0x3015, /* 4977 */ 0x0043, 0x0044, /* 4979 */ 0x0057, 0x005A, /* 4981 */ 0x0048, 0x0056, /* 4983 */ 0x004D, 0x0056, /* 4985 */ 0x0053, 0x0044, /* 4987 */ 0x0053, 0x0053, /* 4989 */ 0x0050, 0x0050, 0x0056, /* 4992 */ 0x0057, 0x0043, /* 4994 */ 0x004D, 0x0043, /* 4996 */ 0x004D, 0x0044, /* 4998 */ 0x004D, 0x0052, /* 5000 */ 0x0044, 0x004A, /* 5002 */ 0x307B, 0x304B, /* 5004 */ 0x30B3, 0x30B3, /* 5006 */ 0x3014, 0x672C, 0x3015, /* 5009 */ 0x3014, 0x4E09, 0x3015, /* 5012 */ 0x3014, 0x4E8C, 0x3015, /* 5015 */ 0x3014, 0x5B89, 0x3015, /* 5018 */ 0x3014, 0x70B9, 0x3015, /* 5021 */ 0x3014, 0x6253, 0x3015, /* 5024 */ 0x3014, 0x76D7, 0x3015, /* 5027 */ 0x3014, 0x52DD, 0x3015, /* 5030 */ 0x3014, 0x6557, 0x3015, /* 5033 */ 0x20122, /* 5034 */ 0x2063A, /* 5035 */ 0x2051C, /* 5036 */ 0x2054B, /* 5037 */ 0x291DF, /* 5038 */ 0x20A2C, /* 5039 */ 0x20B63, /* 5040 */ 0x214E4, /* 5041 */ 0x216A8, /* 5042 */ 0x216EA, /* 5043 */ 0x219C8, /* 5044 */ 0x21B18, /* 5045 */ 0x21DE4, /* 5046 */ 0x21DE6, /* 5047 */ 0x22183, /* 5048 */ 0x2A392, /* 5049 */ 0x22331, /* 5050 */ 0x22331, /* 5051 */ 0x232B8, /* 5052 */ 0x261DA, /* 5053 */ 0x226D4, /* 5054 */ 0x22B0C, /* 5055 */ 0x22BF1, /* 5056 */ 0x2300A, /* 5057 */ 0x233C3, /* 5058 */ 0x2346D, /* 5059 */ 0x236A3, /* 5060 */ 0x238A7, /* 5061 */ 0x23A8D, /* 5062 */ 0x21D0B, /* 5063 */ 0x23AFA, /* 5064 */ 0x23CBC, /* 5065 */ 0x23D1E, /* 5066 */ 0x23ED1, /* 5067 */ 0x23F5E, /* 5068 */ 0x23F8E, /* 5069 */ 0x20525, /* 5070 */ 0x24263, /* 5071 */ 0x243AB, /* 5072 */ 0x24608, /* 5073 */ 0x24735, /* 5074 */ 0x24814, /* 5075 */ 0x24C36, /* 5076 */ 0x24C92, /* 5077 */ 0x2219F, /* 5078 */ 0x24FA1, /* 5079 */ 0x24FB8, /* 5080 */ 0x25044, /* 5081 */ 0x250F3, /* 5082 */ 0x250F2, /* 5083 */ 0x25119, /* 5084 */ 0x25133, /* 5085 */ 0x2541D, /* 5086 */ 0x25626, /* 5087 */ 0x2569A, /* 5088 */ 0x256C5, /* 5089 */ 0x2597C, /* 5090 */ 0x25AA7, /* 5091 */ 0x25AA7, /* 5092 */ 0x25BAB, /* 5093 */ 0x25C80, /* 5094 */ 0x25F86, /* 5095 */ 0x26228, /* 5096 */ 0x26247, /* 5097 */ 0x262D9, /* 5098 */ 0x2633E, /* 5099 */ 0x264DA, /* 5100 */ 0x26523, /* 5101 */ 0x265A8, /* 5102 */ 0x2335F, /* 5103 */ 0x267A7, /* 5104 */ 0x267B5, /* 5105 */ 0x23393, /* 5106 */ 0x2339C, /* 5107 */ 0x26B3C, /* 5108 */ 0x26C36, /* 5109 */ 0x26D6B, /* 5110 */ 0x26CD5, /* 5111 */ 0x273CA, /* 5112 */ 0x26F2C, /* 5113 */ 0x26FB1, /* 5114 */ 0x270D2, /* 5115 */ 0x27667, /* 5116 */ 0x278AE, /* 5117 */ 0x27966, /* 5118 */ 0x27CA8, /* 5119 */ 0x27F2F, /* 5120 */ 0x20804, /* 5121 */ 0x208DE, /* 5122 */ 0x285D2, /* 5123 */ 0x285ED, /* 5124 */ 0x2872E, /* 5125 */ 0x28BFA, /* 5126 */ 0x28D77, /* 5127 */ 0x29145, /* 5128 */ 0x2921A, /* 5129 */ 0x2940A, /* 5130 */ 0x29496, /* 5131 */ 0x295B6, /* 5132 */ 0x29B30, /* 5133 */ 0x2A0CE, /* 5134 */ 0x2A105, /* 5135 */ 0x2A20E, /* 5136 */ 0x2A291, /* 5137 */ 0x2A600 }; odyssey-1.6.0-rc1/sources/include/common_const.h000066400000000000000000000002431526064202200216530ustar00rootroot00000000000000#pragma once #define OD_QRY_MAX_SZ 512 /* odyssey maximum allowed query size */ #define OD_MAX_AVAILABILITY_ZONE_LENGTH 16 #define OD_STORAGE_MAX_ENDPOINTS 128 odyssey-1.6.0-rc1/sources/include/compression.h000066400000000000000000000003241526064202200215160ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_compression_frontend_setup(od_client_t *, od_config_listen_t *, od_logger_t *); odyssey-1.6.0-rc1/sources/include/config.h000066400000000000000000000111701526064202200204230ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #ifdef LDAP_FOUND #define LDAP_MIN_COROUTINE_STACK_SIZE 16 #endif struct od_config_listen { od_tls_opts_t *tls_opts; char *host; int port; int backlog; int client_login_timeout; int compression; od_list_t link; od_target_session_attrs_t target_session_attrs; od_storage_balancing_t balancing_override; int balancing_override_set; int catchup_timeout; char **storage_names; size_t storage_count; int reserved_clients; }; struct od_config_conn_drop_options { int drop_enabled; int rate; int interval_ms; }; struct od_config_soft_oom_drop { int enabled; int signal; int max_rate; }; struct od_config_soft_oom { int enabled; uint64_t limit_bytes; char process[256]; int check_interval_ms; od_config_soft_oom_drop_t drop; }; typedef enum { OD_CONFIG_QUERY_PARSING_MODE_DISABLED, OD_CONFIG_QUERY_PARSING_MODE_MINIMAL, OD_CONFIG_QUERY_PARSING_MODE_FULL, } od_config_query_parsing_mode_t; typedef struct { od_config_query_parsing_mode_t mode; size_t mem_limit_bytes; } od_config_query_parsing_t; struct od_config { int daemonize; int priority; int sequential_routing; /* logging */ int log_to_stdout; int log_debug; int log_config; int log_session; int log_query; int log_queue_depth; int log_async; char *log_file; char *log_format; int log_stats; int log_general_stats_prom; int log_route_stats_prom; int log_syslog; char *log_syslog_ident; char *log_syslog_facility; /* */ int stats_interval; /* system related settings */ char *pid_file; char *unix_socket_dir; char *unix_socket_mode; char *locks_dir; char *external_auth_socket_path; /* sigusr2 etc */ /* TODO: delete this option - online restart is always enabled now */ int graceful_die_on_errors; int graceful_shutdown_timeout_ms; /* TODO: delete this option - online restart is always enabled now */ int enable_online_restart_feature; od_config_conn_drop_options_t conn_drop_options; int bindwith_reuseport; /* */ int readahead; int nodelay; int disable_nolinger; /* TCP KEEPALIVE related settings */ int keepalive; int keepalive_keep_interval; int keepalive_probes; int keepalive_usr_timeout; /* */ int workers; int resolvers; /* client */ int client_max_set; int client_max; int client_max_routing; int accept_rate_limit; int server_login_retry; int reserve_session_server_connection; /* */ int cache_coroutine; int cache_msg_gc_size; int cache_msg_gc_count; int coroutine_stack_size; int system_coroutine_stack_size; int dns_ttl_ms; char *hba_file; /* Soft interval between group checks */ int group_checker_interval; od_list_t listen; int backend_connect_timeout_ms; int cancel_timeout_ms; int cancel_queue_timeout_ms; int cancel_max_inflight; int cancel_rate_limit; int virtual_processing; /* enables some cases for full-virtual query processing */ int virtual_transaction; char availability_zone[OD_MAX_AVAILABILITY_ZONE_LENGTH]; int max_sigterms_to_die; int host_watcher_enabled; od_config_soft_oom_t soft_oom; od_config_query_parsing_t query_parsing; int smart_search_path_enquoting; od_affinity_config_t *cpu_affinity; }; /* longest configuration value the console is willing to print */ #define OD_CONFIG_FIELD_VALUE_MAX 256 typedef enum { OD_CONFIG_FIELD_INT, OD_CONFIG_FIELD_BOOL, OD_CONFIG_FIELD_STRING, OD_CONFIG_FIELD_STRING_INLINE, } od_config_field_type_t; typedef struct { /* name accepted by the configuration parser */ char *key; od_config_field_type_t type; /* offset of the field inside od_config_t */ size_t offset; /* whether RELOAD applies a new value, see od_config_reload() */ int reloadable; } od_config_field_t; size_t od_config_fields_count(void); const od_config_field_t *od_config_field_at(size_t index); void od_config_field_value(od_config_t *config, const od_config_field_t *field, char *buf, size_t size); void od_config_field_default(const od_config_field_t *field, char *buf, size_t size); void od_config_init(od_config_t *); void od_config_free(od_config_t *); void od_config_reload(od_config_t *, od_config_t *); int od_config_validate(od_config_t *, od_logger_t *); void od_config_print(od_config_t *, od_logger_t *); od_config_listen_t *od_config_listen_add(od_config_t *); static inline char *od_config_listen_host_name(od_config_listen_t *listen) { return listen->host == NULL ? "(NULL)" : listen->host; } odyssey-1.6.0-rc1/sources/include/console.h000066400000000000000000000003211526064202200206140ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_console_query(od_client_t *, machine_msg_t *, char *, uint32_t); odyssey-1.6.0-rc1/sources/include/console/000077500000000000000000000000001526064202200204475ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/console/ast.h000066400000000000000000000031441526064202200214110ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Console command parser AST definitions. */ #include #include typedef enum { OD_CONSOLE_NODE_TYPE_INVALID = 0, OD_CONSOLE_NODE_TYPE_SHOW_STMT, OD_CONSOLE_NODE_TYPE_KILL_CLIENT_STMT, OD_CONSOLE_NODE_TYPE_RELOAD_STMT, OD_CONSOLE_NODE_TYPE_PAUSE_STMT, OD_CONSOLE_NODE_TYPE_RESUME_STMT, OD_CONSOLE_NODE_TYPE_SET_STMT, OD_CONSOLE_NODE_TYPE_DROP_STMT, OD_CONSOLE_NODE_TYPE_GC_STMT, } od_console_node_tag_t; typedef struct od_console_node { od_console_node_tag_t type; } od_console_node_t; typedef struct { od_console_node_tag_t type; char *name; char *arg; } od_console_show_stmt_t; typedef struct { od_console_node_tag_t type; char *id; } od_console_kill_client_stmt_t; typedef struct { od_console_node_tag_t type; } od_console_reload_stmt_t; typedef struct { od_console_node_tag_t type; } od_console_pause_stmt_t; typedef struct { od_console_node_tag_t type; } od_console_resume_stmt_t; typedef struct { od_console_node_tag_t type; char *key; char *value; } od_console_set_stmt_t; typedef enum { OD_CONSOLE_DROP_SERVERS = 0, } od_console_drop_target_t; typedef struct { od_console_node_tag_t type; od_console_drop_target_t target; char *path; } od_console_drop_stmt_t; typedef struct { od_console_node_tag_t type; } od_console_gc_stmt_t; od_console_node_t *od_console_node_alloc(od_linear_alloc_t *al, od_console_node_tag_t type, size_t size); void od_console_node_free(od_console_node_t *node); int od_console_node_print(const od_console_node_t *node, char *buf, size_t buflen); odyssey-1.6.0-rc1/sources/include/console/ctx.h000066400000000000000000000013531526064202200214200ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Console command parser context. */ #include #include #include #include typedef void (*od_console_error_cb_t)(const char *msg, void *userdata); typedef struct od_console_parse_ctx { const char *scanbuf; size_t scanbuflen; od_linear_alloc_t *arena; od_console_node_t *result; od_console_error_cb_t error_cb; void *error_cb_userdata; int had_error; jmp_buf fatal_jmp; int fatal_set; } od_console_parse_ctx_t; void od_console_parse_ctx_init(od_console_parse_ctx_t *ctx, const char *input, size_t input_len, od_linear_alloc_t *arena, od_console_error_cb_t error_cb, void *userdata); odyssey-1.6.0-rc1/sources/include/console/keywords.h000066400000000000000000000002401526064202200224630ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int od_console_keyword_lookup(const char *str, size_t len); odyssey-1.6.0-rc1/sources/include/console/parser.h000066400000000000000000000007051526064202200221160ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Console command parser public API. */ #include #include #include #include /* * returns the AST root on success, NULL on parse error. */ od_console_node_t *od_console_parse(const char *input, size_t input_len, od_linear_alloc_t *arena, od_console_error_cb_t error_cb, void *userdata); odyssey-1.6.0-rc1/sources/include/counter.h000066400000000000000000000011601526064202200206330ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #define OD_COUNTER_MAX_POSSIBLE_VALUE 1000UL typedef struct od_counter { size_t size; atomic_uint_fast64_t value_to_count[FLEXIBLE_ARRAY_MEMBER]; } od_counter_t; od_counter_t *od_counter_create(size_t max_value); od_retcode_t od_counter_free(od_counter_t *counter); uint64_t od_counter_get_count(od_counter_t *counter, size_t value); od_retcode_t od_counter_reset(od_counter_t *counter, size_t value); od_retcode_t od_counter_reset_all(od_counter_t *counter); void od_counter_inc(od_counter_t *counter, size_t item); odyssey-1.6.0-rc1/sources/include/cron.h000066400000000000000000000011651526064202200201220ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #ifdef PROM_FOUND #include #endif struct od_cron { uint64_t stat_time_us; od_global_t *global; atomic_uint_fast64_t startup_errors; #ifdef PROM_FOUND od_prom_metrics_t *metrics; #endif machine_wait_flag_t *can_be_freed; machine_wait_flag_t *online; }; od_retcode_t od_cron_init(od_cron_t *); int od_cron_start(od_cron_t *, od_global_t *); od_retcode_t od_cron_stop(od_cron_t *cron); void od_cron_expire_now(od_cron_t *cron); odyssey-1.6.0-rc1/sources/include/daemon.h000066400000000000000000000001471526064202200204230ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ int od_daemonize(void); odyssey-1.6.0-rc1/sources/include/deploy.h000066400000000000000000000002431526064202200204510ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_deploy(od_client_t *, char *); odyssey-1.6.0-rc1/sources/include/dns.h000066400000000000000000000005741526064202200177500ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_getsockaddrname(struct sockaddr *, char *, int, int, int); int od_getaddrname(mm_addrinfo_t *, char *, int, int, int); int od_getpeername(mm_io_t *, char *, int, int, int); int od_getsockname(mm_io_t *, char *, int, int, int); odyssey-1.6.0-rc1/sources/include/ejection.h000066400000000000000000000011611526064202200207550ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* only for online restart */ #include #include typedef struct { /* TODO: eject info is thread private, so mu is useless? */ mm_spinlock_t mu; int interval_ms; int limit; uint64_t *queue; int head; int tail; int size; } od_conn_eject_info; extern od_retcode_t od_conn_eject_info_init(od_conn_eject_info **dst, const od_config_conn_drop_options_t *opts); extern od_retcode_t od_conn_eject_info_free(od_conn_eject_info *ptr); int od_conn_eject_info_try(od_conn_eject_info *info, uint64_t now_ms); odyssey-1.6.0-rc1/sources/include/err_logger.h000066400000000000000000000015341526064202200213100ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #define DEFAULT_ERROR_INTERVAL_NUMBER (5 * 60) struct od_error_logger { size_t nintervals; atomic_size_t current_interval_num; /* ISO C99 flexible array member */ od_counter_t *interval_counters[FLEXIBLE_ARRAY_MEMBER]; }; extern od_retcode_t od_error_logger_store_err(od_error_logger_t *l, size_t err_t); extern od_error_logger_t *od_err_logger_create(size_t intervals_count); static inline od_error_logger_t *od_err_logger_create_default(void) { return od_err_logger_create(DEFAULT_ERROR_INTERVAL_NUMBER); } od_retcode_t od_err_logger_free(od_error_logger_t *err_logger); od_retcode_t od_err_logger_inc_interval(od_error_logger_t *l); size_t od_err_logger_get_aggr_errors_count(od_error_logger_t *l, size_t err_t); odyssey-1.6.0-rc1/sources/include/extension.h000066400000000000000000000020001526064202200211620ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include struct od_extension { od_module_t *modules; od_global_t *glob; }; static inline od_retcode_t od_extensions_init(od_extension_t *extensions) { extensions->modules = od_malloc(sizeof(od_module_t)); if (extensions->modules == NULL) { return 1; } od_modules_init(extensions->modules); return OK_RESPONSE; } static inline od_extension_t *od_extensions_create(void) { od_extension_t *e = od_malloc(sizeof(od_extension_t)); if (e == NULL) { return NULL; } if (od_extensions_init(e)) { od_free(e); return NULL; } return e; } static inline od_retcode_t od_extension_free(od_logger_t *l, od_extension_t *extensions) { if (extensions == NULL) { return OK_RESPONSE; } if (extensions->modules) { od_modules_unload(l, extensions->modules); } od_free(extensions->modules); extensions->modules = NULL; od_free(extensions); return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/include/external_auth.h000066400000000000000000000005701526064202200220230ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef enum { OD_EAUTH_ERROR = -1, OD_EAUTH_OK = 0, OD_EAUTH_DENIED = 1, } od_external_auth_status_t; od_external_auth_status_t external_user_authentication(char *username, char *token, od_instance_t *instance, od_client_t *client); odyssey-1.6.0-rc1/sources/include/frontend.h000066400000000000000000000074751526064202200210120ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #define MAX_STARTUP_ATTEMPTS 7 static inline machine_msg_t *od_frontend_error_msg(od_client_t *client, machine_msg_t *stream, char *code, char *fmt, va_list args) { char msg[OD_QRY_MAX_SZ]; int msg_len; int show_id = client != NULL; if (client != NULL && client->rule != NULL) { show_id = client->rule->client_show_id; } if (show_id) { msg_len = od_snprintf(msg, sizeof(msg), "odyssey: %s%.*s: ", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); } else { msg_len = od_snprintf(msg, sizeof(msg), "odyssey: "); } msg_len += od_vsnprintf(msg + msg_len, sizeof(msg) - msg_len, fmt, args); return kiwi_be_write_error(stream, code, msg, msg_len); } static inline machine_msg_t * od_frontend_fatal_msg(od_client_t *client, machine_msg_t *stream, const char *code, const char *detail, const char *hint, const char *msg_fmt, va_list args) { char msg[OD_QRY_MAX_SZ]; int msg_len; int show_id = 1; if (client->rule != NULL) { show_id = client->rule->client_show_id; } if (show_id) { msg_len = od_snprintf(msg, sizeof(msg), "odyssey: %s%.*s: ", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); } else { msg_len = od_snprintf(msg, sizeof(msg), "odyssey: "); } msg_len += od_vsnprintf(msg + msg_len, sizeof(msg) - msg_len, msg_fmt, args); return kiwi_be_write_error_fatal(stream, code, detail, strlen(detail), hint, strlen(hint), msg, msg_len); } static inline machine_msg_t *od_frontend_errorf(od_client_t *client, machine_msg_t *stream, char *code, char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_error_msg(client, stream, code, fmt, args); va_end(args); return msg; } static inline machine_msg_t *od_frontend_info_msg(od_client_t *client, machine_msg_t *stream, char *fmt, va_list args) { char msg[OD_QRY_MAX_SZ]; int msg_len; int show_id = 1; if (client->rule != NULL) { show_id = client->rule->client_show_id; } if (show_id) { msg_len = od_snprintf(msg, sizeof(msg), "odyssey: %s%.*s: ", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); } else { msg_len = od_snprintf(msg, sizeof(msg), "odyssey: "); } msg_len += od_vsnprintf(msg + msg_len, sizeof(msg) - msg_len, fmt, args); return kiwi_be_write_notice_info(stream, msg, msg_len); } static inline machine_msg_t * od_frontend_infof(od_client_t *client, machine_msg_t *stream, char *fmt, ...) { va_list args; va_start(args, fmt); machine_msg_t *msg; msg = od_frontend_info_msg(client, stream, fmt, args); va_end(args); return msg; } int od_frontend_info(od_client_t *, char *, ...); int od_frontend_error(od_client_t *, char *, char *, ...); int od_frontend_fatal(od_client_t *, char *, char *, ...); int od_frontend_fatal_no_startup(mm_io_t *, char *, char *, ...); int od_frontend_fatal_detailed(od_client_t *client, const char *code, const char *detail, const char *hint, const char *fmt, ...); void od_frontend(void *); typedef struct { od_storage_endpoint_t *endpoint; int priority; } od_endpoint_attach_candidate_t; void od_frontend_attach_init_candidates( od_instance_t *instance, od_rule_storage_t *storage, od_endpoint_attach_candidate_t *candidates, od_target_session_attrs_t tsa, int prefer_localhost); od_frontend_status_t od_frontend_attach(od_client_t *client, char *context, kiwi_params_t *route_params); od_frontend_status_t od_frontend_attach_and_deploy(od_client_t *client, char *context); odyssey-1.6.0-rc1/sources/include/global.h000066400000000000000000000047661526064202200204330ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include struct od_global { od_instance_t *instance; od_system_t *system; od_router_t *router; od_cron_t *cron; od_worker_pool_t *worker_pool; od_extension_t *extensions; od_hba_t *hba; od_soft_oom_checker_t soft_oom; od_host_watcher_t host_watcher; atomic_uint_fast64_t pause; mm_wait_list_t *resume_waiters; od_rate_limiter_t accept_rate_limiter; mm_sem_t cancel_sem; od_rate_limiter_t cancel_rate_limiter; mm_sem_t routing_sem; }; od_global_t *od_global_create(od_instance_t *instance, od_system_t *system, od_router_t *router, od_cron_t *cron, od_worker_pool_t *worker_pool, od_extension_t *extensions, od_hba_t *hba); int od_global_init(od_global_t *global, od_instance_t *instance, od_system_t *system, od_router_t *router, od_cron_t *cron, od_worker_pool_t *worker_pool, od_extension_t *extensions, od_hba_t *hba); void od_global_set(od_global_t *global); od_global_t *od_global_get(void); od_logger_t *od_global_get_logger(void); od_instance_t *od_global_get_instance(void); static inline void od_global_destroy(od_global_t *global) { mm_wait_list_free(global->resume_waiters); od_rate_limiter_destroy(&global->accept_rate_limiter); od_rate_limiter_destroy(&global->cancel_rate_limiter); od_free(global); od_global_set(NULL); } static inline uint64_t od_global_is_paused(od_global_t *global) { return atomic_load_explicit(&global->pause, memory_order_relaxed); } static inline void od_global_pause(od_global_t *global) { atomic_store_explicit(&global->pause, 1ULL, memory_order_relaxed); } static inline void od_global_resume(od_global_t *global) { atomic_store_explicit(&global->pause, 0ULL, memory_order_relaxed); mm_wait_list_notify_all(global->resume_waiters); } static inline int od_global_wait_resumed(od_global_t *global, uint32_t timeout) { if (!od_global_is_paused(global)) { return 0; } int rc = mm_wait_list_wait(global->resume_waiters, NULL, timeout); if (rc == 0) { return 0; } return 1; } int od_global_is_in_soft_oom(od_global_t *global, uint64_t *used_memory); void od_global_read_host_utilization(od_global_t *global, float *cpu, float *mem); int od_routing_slot_acquire(od_global_t *global, uint32_t timeout_ms); void od_routing_slot_release(od_global_t *global); odyssey-1.6.0-rc1/sources/include/group.h000066400000000000000000000013341526064202200203130ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef struct od_group od_group_t; struct od_group { char *route_usr; char *route_db; char *group_name; char *group_query; char *group_query_user; char *group_query_db; int check_retry; int online; od_global_t *global; od_list_t link; }; typedef struct od_group_member_name_item od_group_member_name_item_t; struct od_group_member_name_item { char *value; int is_checked; od_list_t link; }; int od_group_free(od_group_t *); int od_group_parse_val_datarow(machine_msg_t *, char **); od_group_member_name_item_t *od_group_member_name_item_add(od_list_t *); odyssey-1.6.0-rc1/sources/include/hba.h000066400000000000000000000007141526064202200177120ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include typedef struct od_logger od_logger_t; struct od_hba { mm_mutex_t lock; od_hba_rules_t rules; }; void od_hba_init(od_hba_t *hba); void od_hba_free(od_hba_t *hba); void od_hba_reload(od_hba_t *hba, od_hba_rules_t *rules); void od_hba_rules_print(od_hba_t *hba, od_logger_t *logger); int od_hba_process(od_client_t *client); odyssey-1.6.0-rc1/sources/include/hba_rule.h000066400000000000000000000023641526064202200207440ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #define OD_HBA_NAME_ALL 1 #define OD_HBA_NAME_SAMEUSER 2 typedef struct od_hba_rule od_hba_rule_t; typedef enum { OD_CONFIG_HBA_LOCAL, OD_CONFIG_HBA_HOST, OD_CONFIG_HBA_HOSTSSL, OD_CONFIG_HBA_HOSTNOSSL } od_hba_rule_conn_type_t; typedef enum { OD_CONFIG_HBA_ALLOW, OD_CONFIG_HBA_DENY, } od_hba_rule_auth_method_t; typedef struct od_hba_rule_name_item od_hba_rule_name_item_t; struct od_hba_rule_name_item { char *value; od_list_t link; }; typedef struct od_hba_rule_name od_hba_rule_name_t; struct od_hba_rule_name { unsigned int flags; od_list_t values; }; struct od_hba_rule { od_hba_rule_conn_type_t connection_type; od_hba_rule_name_t database; od_hba_rule_name_t user; od_address_range_t address_range; od_hba_rule_auth_method_t auth_method; od_list_t link; }; typedef od_list_t od_hba_rules_t; od_hba_rule_name_item_t *od_hba_rule_name_item_add(od_hba_rule_name_t *name); od_hba_rule_t *od_hba_rule_create(void); void od_hba_rule_free(od_hba_rule_t *hba); void od_hba_rules_init(od_hba_rules_t *rules); void od_hba_rules_free(od_hba_rules_t *rules); void od_hba_rules_add(od_hba_rules_t *rules, od_hba_rule_t *rule); odyssey-1.6.0-rc1/sources/include/host_watcher.h000066400000000000000000000012741526064202200216540ustar00rootroot00000000000000#pragma once #include #include #include typedef struct { uint64_t total; uint64_t idle; uint64_t total_diff; uint64_t idle_diff; } od_cpu_stat_t; typedef struct { uint64_t total; uint64_t available; } od_mem_stat_t; typedef struct { long hz; mm_spinlock_t lock; od_cpu_stat_t cpu_stat; od_mem_stat_t mem_stat; machine_wait_flag_t *stop_flag; int64_t worker_id; } od_host_watcher_t; /* starts separated thread, because it uses file i/o */ int od_host_watcher_init(od_host_watcher_t *hw); void od_host_watcher_destroy(od_host_watcher_t *hw); void od_host_watcher_read(od_host_watcher_t *hw, float *cpu, float *mem); odyssey-1.6.0-rc1/sources/include/id.h000066400000000000000000000021771526064202200175610ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef struct od_id od_id_t; typedef struct od_id_mgr od_id_mgr_t; #define OD_ID_SEEDMAX 6 #define OD_ID_LEN (OD_ID_SEEDMAX * 2) struct od_id { char *id_prefix; char id[OD_ID_LEN]; uint64_t id_a; uint64_t id_b; }; static inline int od_id_cmp(od_id_t *a, od_id_t *b) { return memcmp(a->id, b->id, sizeof(a->id)) == 0; } static inline void od_id_generate(od_id_t *id, char *prefix) { long int a = machine_lrand48(); long int b = machine_lrand48(); char seed[OD_ID_SEEDMAX]; memcpy(seed + 0, &a, 4); memcpy(seed + 4, &b, 2); id->id_prefix = prefix; id->id_a = a; id->id_b = b; static const char *hex = "0123456789abcdef"; int q, w; for (q = 0, w = 0; q < OD_ID_SEEDMAX; q++) { id->id[w++] = hex[(seed[q] >> 4) & 0x0F]; id->id[w++] = hex[(seed[q]) & 0x0F]; } #if OD_DEVEL_LVL != -1 od_assert(w == (OD_ID_SEEDMAX * 2)); #endif } void od_id_generator_seed(void); static inline void od_id_write_to_string(od_id_t *id, char *dest, int max) { snprintf(dest, max, "%s%.*s", id->id_prefix, OD_ID_LEN, id->id); } odyssey-1.6.0-rc1/sources/include/instance.h000066400000000000000000000021431526064202200207620ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include typedef struct timeval od_timeval_t; struct od_instance { od_pid_t pid; od_logger_t logger; char *config_file; char *exec_path; od_config_t config; char *orig_argv_ptr; int orig_argv_ptr_len; atomic_int_fast64_t shutdown_worker_id; /* last config load failed, so a restart would not come up */ atomic_int config_load_failed; struct { int argc; char **argv; char **envp; } cmdline; od_global_pstmt_map_t *pstmts; }; od_instance_t *od_instance_create(void); void od_instance_free(od_instance_t *); int od_instance_main(od_instance_t *instance, int argc, char **argv, char **envp); char *od_instance_getenv(od_instance_t *instance, const char *name); void od_instance_set_shutdown_worker_id(od_instance_t *instance, int64_t id); int64_t od_instance_get_shutdown_worker_id(od_instance_t *instance); od_global_pstmt_map_t *od_instance_get_pstmts_map(od_instance_t *instance); odyssey-1.6.0-rc1/sources/include/internal_client.h000066400000000000000000000024421526064202200223320ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include static inline od_client_t *od_client_allocate_internal(od_global_t *global, char *context) { od_client_t *internal_client; internal_client = od_client_allocate(); if (internal_client == NULL) { return NULL; } od_instance_t *instance = global->instance; internal_client->global = global; internal_client->type = OD_POOL_CLIENT_INTERNAL; od_id_generate(&internal_client->id, "ic"); /* create io handle */ mm_io_t *io; io = mm_io_create(); if (io == NULL) { od_client_free(internal_client); return NULL; } /* set network options */ mm_io_set_nodelay(io, instance->config.nodelay); if (instance->config.keepalive > 0) { mm_io_set_keepalive(io, 1, instance->config.keepalive, instance->config.keepalive_keep_interval, instance->config.keepalive_probes, instance->config.keepalive_usr_timeout); } int rc; rc = od_io_prepare(&internal_client->io, io); if (rc == -1) { od_error(&instance->logger, context, internal_client, NULL, "failed to setup internal client io"); mm_io_close(io); mm_io_free(io); od_client_free(internal_client); return NULL; } return internal_client; } odyssey-1.6.0-rc1/sources/include/io.h000066400000000000000000000123401526064202200175650ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include typedef struct od_io od_io_t; struct od_io { od_readahead_t readahead; mm_io_t *io; }; static inline void od_io_init(od_io_t *io) { io->io = NULL; od_readahead_init(&io->readahead); } static inline void od_io_free(od_io_t *io) { od_readahead_free(&io->readahead); } static inline char *od_io_error(od_io_t *io) { return mm_io_error(io->io); } static inline int od_io_prepare(od_io_t *io, mm_io_t *io_obj) { int rc; rc = od_readahead_prepare(&io->readahead); if (rc == -1) { return -1; } io->io = io_obj; return 0; } static inline int od_io_close(od_io_t *io) { if (io->io == NULL) { return -1; } int rc = mm_io_close(io->io); mm_io_free(io->io); io->io = NULL; return rc; } static inline int od_io_attach(od_io_t *io) { return mm_io_attach(io->io); } static inline int od_io_detach(od_io_t *io) { return mm_io_detach(io->io); } static inline int od_io_poll(od_io_t *io) { return mm_io_poll(io->io); } static inline void od_io_set_peer(od_io_t *io, od_io_t *peer) { mm_io_set_peer((mm_io_t *)io->io, (mm_io_t *)peer->io); } static inline void od_io_remove_peer(od_io_t *io, od_io_t *peer) { mm_io_remove_peer((mm_io_t *)io->io, (mm_io_t *)peer->io); } static inline int od_io_read(od_io_t *io, char *dest, int size, uint32_t time_ms) { int pos = 0; int rc; for (;;) { int nread = (int)od_readahead_read(&io->readahead, dest + pos, (size_t)size); size -= nread; pos += nread; if (size == 0) { break; } for (;;) { struct iovec vec = od_readahead_write_begin(&io->readahead); rc = machine_read_raw(io->io, vec.iov_base, vec.iov_len); if (rc <= 0) { /* retry using read condition wait */ int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { rc = mm_io_wait((mm_io_t *)io->io, time_ms); if (rc == MM_COND_WAIT_FAIL) { /* io wait will set errno to ETIMEDOUT or ECANCELLED */ return -1; } if (rc == MM_COND_WAIT_OK_PROPAGATED) { mm_errno_set(EAGAIN); return -1; } continue; } /* error or unexpected eof */ return -1; } od_readahead_write_commit(&io->readahead, (size_t)rc); break; } } return 0; } static inline machine_msg_t *od_read_startup(od_io_t *io, uint32_t time_ms) { uint32_t header; int rc; rc = od_io_read(io, (char *)&header, sizeof(header), time_ms); if (rc == -1) { return NULL; } /* pre-validate startup header size, actual header parsing will be done by * kiwi_be_read_startup() */ uint32_t size; rc = kiwi_validate_startup_header((char *)&header, sizeof(header), &size); if (rc == -1) { return NULL; } machine_msg_t *msg; msg = machine_msg_create(sizeof(header) + size + 1 /* for extra 0 */); if (msg == NULL) { return NULL; } char *dest; dest = machine_msg_data(msg); memcpy(dest, &header, sizeof(header)); dest += sizeof(header); /* zero-terminate for possible not terminated strings */ dest[size] = '\0'; rc = od_io_read(io, dest, size, time_ms); if (rc == -1) { machine_msg_free(msg); return NULL; } return msg; } typedef enum { OD_READ_FE, OD_READ_BE, } od_read_msg_type_t; static inline machine_msg_t *od_read(od_io_t *io, uint32_t time_ms, od_read_msg_type_t type) { kiwi_header_t header; int rc; rc = od_io_read(io, (char *)&header, sizeof(header), time_ms); if (rc == -1) { return NULL; } /* pre-validate packet header */ uint32_t size; if (type == OD_READ_FE) { rc = kiwi_validate_fe_header((char *)&header, sizeof(header), &size); } else if (type == OD_READ_BE) { rc = kiwi_validate_be_header((char *)&header, sizeof(header), &size); } else { abort(); } if (rc == -1) { return NULL; } size -= sizeof(uint32_t); machine_msg_t *msg; msg = machine_msg_create(sizeof(header) + size); if (msg == NULL) { return NULL; } char *dest; dest = machine_msg_data(msg); memcpy(dest, &header, sizeof(header)); dest += sizeof(header); rc = od_io_read(io, dest, size, time_ms); if (rc == -1) { machine_msg_free(msg); return NULL; } return msg; } static inline int od_write(od_io_t *io, machine_msg_t *msg) { return machine_write(io->io, msg, UINT32_MAX); } static inline int od_write2(od_io_t *io, machine_msg_t *msg, uint32_t timeout_ms) { return machine_write(io->io, msg, timeout_ms); } int od_io_read_some(od_io_t *io, uint32_t timeout_ms); int od_io_try_read_some(od_io_t *io); /* * Note: this function do not frees msg */ static inline int od_io_write(od_io_t *io, machine_msg_t *msg, uint32_t timeout_ms) { return machine_write_no_free(io->io, msg, timeout_ms); } static inline int od_io_connected(od_io_t *io) { return mm_io_connected(io->io); } enum { OD_IO_WRITE_DUPLEX = 1 << 0, }; int od_io_write_raw(od_io_t *io, const void *buf, size_t size, size_t *processed, uint32_t timeout_ms, int flags); /* breaks iovec arg */ int od_io_writev(od_io_t *io, struct iovec *iov, int iovcnt, uint32_t timeout_ms, int flags); static inline int od_io_last_event(od_io_t *io) { return mm_io_last_event(io->io); } int od_io_write_flush(od_io_t *io, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/kiwi/000077500000000000000000000000001526064202200177505ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/kiwi/be_read.h000066400000000000000000000332321526064202200215050ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_be_startup kiwi_be_startup_t; struct kiwi_be_startup { int is_ssl_request; int unsupported_request; int is_cancel; uint32_t proto_version; kiwi_key_t key; kiwi_var_t user; kiwi_var_t database; kiwi_var_t replication; }; static inline void kiwi_be_startup_init(kiwi_be_startup_t *su) { su->is_cancel = 0; su->proto_version = 0; su->is_ssl_request = 0; su->unsupported_request = 0; kiwi_key_init(&su->key); kiwi_var_init(&su->user, NULL, 0); kiwi_var_init(&su->database, NULL, 0); kiwi_var_init(&su->replication, NULL, 0); } static inline int kiwi_be_read_options(kiwi_be_startup_t *su, char *pos, uint32_t pos_size, kiwi_vars_t *vars) { for (;;) { /* name */ uint32_t name_size; char *name = pos; int rc; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } name_size = pos - name; if (name_size == 1) { break; } /* value */ uint32_t value_size; char *value = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } value_size = pos - value; /* set common params */ if (name_size == 5 && !memcmp(name, "user", 5)) { kiwi_var_set(&su->user, KIWI_VAR_UNDEF, value, value_size); } else if (name_size == 9 && !memcmp(name, "database", 9)) { kiwi_var_set(&su->database, KIWI_VAR_UNDEF, value, value_size); } else if (name_size == 12 && !memcmp(name, "replication", 12)) { kiwi_var_set(&su->replication, KIWI_VAR_UNDEF, value, value_size); } else if (name_size == 8 && !memcmp(name, "options", 8)) { rc = kiwi_parse_options_and_update_vars(vars, value, value_size); if (kiwi_unlikely(rc == -1)) { return -1; } } else { kiwi_vars_update(vars, name, name_size, value, value_size); } } /* user is mandatory */ if (su->user.value_len == 0) { return -1; } /* database = user, if not specified */ if (su->database.value_len == 0) { kiwi_var_set(&su->database, KIWI_VAR_UNDEF, su->user.value, su->user.value_len); } return 0; } #define PG_PROTOCOL(m, n) (((m) << 16) | (n)) #define PG_PROTOCOL_MAJOR(v) ((int)((v) >> 16)) #define PG_PROTOCOL_MINOR(v) ((int)((v) & 0x0000ffff)) #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234, 5679) #define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234, 5680) #define CANCEL_REQUEST_CODE PG_PROTOCOL(1234, 5678) #define PG_PROTOCOL_LATEST PG_PROTOCOL(3, 0) #define PG_PROTOCOL_EARLIEST PG_PROTOCOL(3, 0) enum { KIWI_STARTUP_READ_OK = 0, KIWI_STARTUP_READ_LEN_ERROR, KIWI_STARTUP_READ_VERSION_ERROR, KIWI_STARTUP_CANCEL_READ_ERROR, KIWI_STARTUP_READ_OPTIONS_ERROR, KIWI_STARTUP_INVALID_MAJOR_ERROR, }; KIWI_API static inline int kiwi_be_read_cancel_request(char **pos, uint32_t *pos_size, kiwi_be_startup_t *su) { if (kiwi_unlikely(kiwi_read32(&su->key.key_pid, pos, pos_size) == -1)) { return -1; } if (kiwi_unlikely(kiwi_read32(&su->key.key, pos, pos_size) == -1)) { return -1; } return 0; } KIWI_API static inline int kiwi_protocol_major_supported(uint32_t version) { return PG_PROTOCOL_MAJOR(version) >= PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) && PG_PROTOCOL_MAJOR(version) <= PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST); } KIWI_API static inline int kiwi_protocol_minor_supported(uint32_t version) { return PG_PROTOCOL_MINOR(version) >= PG_PROTOCOL_MINOR(PG_PROTOCOL_EARLIEST) && PG_PROTOCOL_MINOR(version) <= PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST); } KIWI_API static inline int kiwi_be_read_startup(char *data, uint32_t size, kiwi_be_startup_t *su, kiwi_vars_t *vars) { uint32_t pos_size = size; char *pos = data; int rc; uint32_t len; rc = kiwi_read32(&len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return KIWI_STARTUP_READ_LEN_ERROR; } uint32_t version; rc = kiwi_read32(&version, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return KIWI_STARTUP_READ_VERSION_ERROR; } su->unsupported_request = 0; switch (version) { /* CancelRequest */ case CANCEL_REQUEST_CODE: su->is_cancel = 1; rc = kiwi_be_read_cancel_request(&pos, &pos_size, su); if (rc == -1) { return KIWI_STARTUP_CANCEL_READ_ERROR; } break; /* SSLRequest */ case NEGOTIATE_SSL_CODE: su->is_ssl_request = 1; break; /* GSSRequest */ case NEGOTIATE_GSS_CODE: su->unsupported_request = 1; break; default: /* StartupMessage */ su->proto_version = version; if (!kiwi_protocol_major_supported(version)) { return KIWI_STARTUP_INVALID_MAJOR_ERROR; } rc = kiwi_be_read_options(su, pos, pos_size, vars); if (kiwi_unlikely(rc == -1)) { return KIWI_STARTUP_READ_OPTIONS_ERROR; } break; } return KIWI_STARTUP_READ_OK; } KIWI_API static inline int kiwi_be_read_password(char *data, uint32_t size, kiwi_password_t *pw) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_PASSWORD_MESSAGE)) { return -1; } if (len > PQ_SMALL_MESSAGE_LIMIT) { return -1; } pw->password_len = len; pw->password = malloc(len); if (pw->password == NULL) { return -1; } memcpy(pw->password, kiwi_header_data(header), len); return 0; } KIWI_API static inline int kiwi_be_read_query(char *data, uint32_t size, char **query, uint32_t *query_len) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_QUERY)) { return -1; } /* * The query string must be NUL-terminated exactly at the end of * the message. A client may send a message without the trailing * zero (or an empty one): without the check the string is later * parsed by functions relying on strlen(), running past the end * of the message buffer (heap-buffer-overflow read). */ if (kiwi_unlikely(len == 0)) { return -1; } char *query_str = kiwi_header_data(header); if (kiwi_unlikely(query_str[len - 1] != '\0')) { return -1; } *query = query_str; *query_len = len; return 0; } KIWI_API static inline int kiwi_be_parse_opname_offset(char *data, __attribute__((unused)) int size) { /* offset in bytes of operator name start */ kiwi_header_t *header = (kiwi_header_t *)data; if (kiwi_unlikely(header->type != KIWI_FE_PARSE)) { return -1; } char *pos = kiwi_header_data(header); /* operator_name */ return pos - data; } typedef struct { char *operator_name; size_t operator_name_len; void *description; size_t description_len; } kiwi_prepared_statement_t; KIWI_API static inline int kiwi_be_read_parse_dest(char *data, uint32_t size, kiwi_prepared_statement_t *dest) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_PARSE)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); /* operator_name */ char *opname = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } dest->operator_name_len = pos - opname; dest->operator_name = opname; /* query and params */ dest->description_len = pos_size; dest->description = pos; /* * The description holds "query\0" followed by an int16 parameter * type count (and int32 oids). Require a NUL-terminated query: * the stored description is later parsed with strlen-based * routines, a message without the terminator makes them read * past the end of the buffer. */ if (kiwi_unlikely(pos_size < 3)) { return -1; } if (kiwi_unlikely(memchr(pos, '\0', pos_size - 2) == NULL)) { return -1; } return 0; } KIWI_API static inline int kiwi_be_read_parse(char *data, uint32_t size, char **name, uint32_t *name_len, char **query, uint32_t *query_len) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_PARSE)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); /* operator_name */ *name = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *name_len = pos - *name; /* query */ *query = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *query_len = pos - *query; /* typec */ /* u16 */ /* typev */ /* u32 */ return 0; } KIWI_API static inline int kiwi_be_bind_opname_offset(char *data, uint32_t size) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_BIND)) { return -1; } /* destination portal */ uint32_t pos_size = len; char *pos = kiwi_header_data(header); rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } /* source prepared statement */ return pos - (char *)header; } KIWI_API static inline int kiwi_be_describe_opname_offset(char *data, uint32_t size) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_DESCRIBE)) { return -1; } char type; uint32_t pos_size = len; char *pos = kiwi_header_data(header); rc = kiwi_read8(&type, &pos, &pos_size); if (kiwi_unlikely(rc != 0)) { return -1; } /* operator_name */ return pos - (char *)header; } KIWI_API static inline int kiwi_be_read_describe(char *data, uint32_t size, char **name, uint32_t *name_len, kiwi_fe_describe_type_t *type) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_DESCRIBE)) { return -1; } char t_type; uint32_t pos_size = len; char *pos = kiwi_header_data(header); rc = kiwi_read8(&t_type, &pos, &pos_size); if (kiwi_unlikely(rc != 0)) { return -1; } *type = t_type; /* operator_name */ *name = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *name_len = pos - *name; return 0; } KIWI_API static inline int kiwi_be_read_execute(char *data, uint32_t size, char **name, uint32_t *name_len) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_EXECUTE)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); /* operator_name */ *name = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *name_len = pos - *name; return 0; } KIWI_API static inline int kiwi_be_read_close(char *data, uint32_t size, char **name, uint32_t *name_len, kiwi_fe_close_type_t *type) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_CLOSE)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); char t_type; rc = kiwi_read8(&t_type, &pos, &pos_size); if (kiwi_unlikely(rc != 0)) { return -1; } *type = (kiwi_fe_close_type_t)t_type; /* operator_name */ *name = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *name_len = pos - *name; return 0; } KIWI_API static inline int kiwi_be_read_bind_names(char *data, uint32_t size, char **portal_name, uint32_t *portal_name_len, char **stmt_name, uint32_t *stmt_name_len) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_BIND)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); /* destination portal */ char *p = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } if (portal_name) { *portal_name = p; *portal_name_len = pos - p; } /* source prepared statement */ char *s = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } if (stmt_name) { *stmt_name = s; *stmt_name_len = pos - s; } return 0; } KIWI_API static inline int kiwi_be_read_authentication_sasl_initial(char *data, uint32_t size, char **mechanism, char **auth_data, size_t *auth_data_size) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_PASSWORD_MESSAGE)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); *mechanism = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } uint32_t auth_data_len; rc = kiwi_read32(&auth_data_len, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } if (kiwi_unlikely(auth_data_len != pos_size)) { return -1; } *auth_data = pos; *auth_data_size = pos_size; return 0; } KIWI_API static inline int kiwi_be_read_authentication_sasl(char *data, uint32_t size, char **auth_data, size_t *auth_data_size) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_FE_PASSWORD_MESSAGE)) { return -1; } *auth_data = kiwi_header_data(header); *auth_data_size = len; return 0; } odyssey-1.6.0-rc1/sources/include/kiwi/be_write.h000066400000000000000000000532471526064202200217340ustar00rootroot00000000000000#pragma once #define NULL_MSG_LEN -1 /* * kiwi. * * postgreSQL protocol interaction library. */ KIWI_API static inline machine_msg_t * kiwi_be_write_error_as(machine_msg_t *msg, const char *severity, const char *code, const char *detail, int detail_len, const char *hint, int hint_len, const char *message, int len) { size_t size = 1 /* S */ + 6 + 1 /* C */ + 6 + 1 /* M */ + len + 1 + 1 /* zero */; if (detail && detail_len > 0) { size += 1 + /* D */ +detail_len + 1; } if (hint && hint_len > 0) { size += 1 + /* H */ +hint_len + 1; } int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, sizeof(kiwi_header_t) + size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_ERROR_RESPONSE); kiwi_write32(&pos, sizeof(uint32_t) + size); kiwi_write8(&pos, 'S'); kiwi_write(&pos, severity, 6); kiwi_write8(&pos, 'C'); kiwi_write(&pos, code, 6); if (detail && detail_len > 0) { kiwi_write8(&pos, 'D'); kiwi_write(&pos, detail, detail_len); kiwi_write8(&pos, 0); } if (hint && hint_len > 0) { kiwi_write8(&pos, 'H'); kiwi_write(&pos, hint, hint_len); kiwi_write8(&pos, 0); } kiwi_write8(&pos, 'M'); kiwi_write(&pos, message, len); kiwi_write8(&pos, 0); kiwi_write8(&pos, 0); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_error(machine_msg_t *msg, char *code, char *message, int len) { return kiwi_be_write_error_as(msg, "ERROR", code, NULL, 0, NULL, 0, message, len); } KIWI_API static inline machine_msg_t * kiwi_be_write_error_fatal(machine_msg_t *msg, const char *code, const char *detail, int detlen, const char *hint, int hintlen, const char *message, int len) { return kiwi_be_write_error_as(msg, "FATAL", code, detail, detlen, hint, hintlen, message, len); } KIWI_API static inline machine_msg_t * kiwi_be_write_error_panic(machine_msg_t *msg, char *code, char *message, int len) { return kiwi_be_write_error_as(msg, "PANIC", code, NULL, 0, NULL, 0, message, len); } KIWI_API static inline machine_msg_t * kiwi_be_write_notice_as(machine_msg_t *msg, char *severity, int severity_len, char *code, char *detail, int detail_len, char *hint, int hint_len, char *message, int len) { size_t size = 1 /* S */ + severity_len + 1 /* C */ + 6 + 1 /* M */ + len + 1 + 1 /* zero */; if (detail && detail_len > 0) { size += 1 + /* D */ +detail_len + 1; } if (hint && hint_len > 0) { size += 1 + /* H */ +hint_len + 1; } int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, sizeof(kiwi_header_t) + size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_NOTICE_RESPONSE); kiwi_write32(&pos, sizeof(uint32_t) + size); kiwi_write8(&pos, 'S'); kiwi_write(&pos, severity, severity_len); kiwi_write8(&pos, 'C'); kiwi_write(&pos, code, 6); if (detail && detail_len > 0) { kiwi_write8(&pos, 'D'); kiwi_write(&pos, detail, detail_len); kiwi_write8(&pos, 0); } if (hint && hint_len > 0) { kiwi_write8(&pos, 'H'); kiwi_write(&pos, hint, hint_len); kiwi_write8(&pos, 0); } kiwi_write8(&pos, 'M'); kiwi_write(&pos, message, len); kiwi_write8(&pos, 0); kiwi_write8(&pos, 0); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_notice_console_usage(machine_msg_t *msg, char *detail) { return kiwi_be_write_notice_as(msg, "NOTICE", 7, KIWI_SUCCESSFUL_COMPLETION, NULL, 0, NULL, 0, detail, strlen(detail)); } KIWI_API static inline machine_msg_t * kiwi_be_write_notice_info(machine_msg_t *msg, char *message, int len) { return kiwi_be_write_notice_as(msg, "INFO", 4, KIWI_SUCCESSFUL_COMPLETION, NULL, 0, NULL, 0, message, len); } KIWI_API static inline machine_msg_t * kiwi_be_write_authentication_ok(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_AUTHENTICATION); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint32_t)); kiwi_write32(&pos, 0); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_authentication_clear_text(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_AUTHENTICATION); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint32_t)); kiwi_write32(&pos, 3); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_authentication_md5(machine_msg_t *msg, char salt[4]) { size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t) + 4; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_AUTHENTICATION); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint32_t) + 4); kiwi_write32(&pos, 5); kiwi_write(&pos, salt, 4); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_authentication_sasl(machine_msg_t *msg, char **mechanism, size_t mechanism_cnt) { size_t total_mechanism_len = 0; size_t mechanism_len; for (size_t i = 0; i < mechanism_cnt; ++i) { total_mechanism_len += strlen(mechanism[i]); } size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t) + total_mechanism_len + mechanism_cnt * sizeof(uint8_t) /* zero-bytes at the end of each mechanism*/ + sizeof(uint8_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_AUTHENTICATION); kiwi_write32(&pos, size - sizeof(uint8_t)); kiwi_write32(&pos, KIWI_BE_SASL_INIT); for (size_t i = 0; i < mechanism_cnt; ++i) { mechanism_len = strlen(mechanism[i]); kiwi_write(&pos, mechanism[i], mechanism_len); kiwi_write8(&pos, 0); /* write mechanism as a string */ } kiwi_write8(&pos, 0); /* mimic the server response of PostgreSQL, zero byte after last auth mechanism indicates that list ended */ return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_authentication_sasl_continue(machine_msg_t *msg, char *data, int data_len) { size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t) + data_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_AUTHENTICATION); kiwi_write32(&pos, size - sizeof(uint8_t)); kiwi_write32(&pos, KIWI_BE_SASL_CONTINUE); kiwi_write(&pos, data, data_len); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_authentication_sasl_final(machine_msg_t *msg, char *data, int data_len) { size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t) + data_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_AUTHENTICATION); kiwi_write32(&pos, size - sizeof(uint8_t)); kiwi_write32(&pos, KIWI_BE_SASL_FINAL); kiwi_write(&pos, data, data_len); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_backend_key_data(machine_msg_t *msg, uint32_t pid, uint32_t key) { size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t) + sizeof(uint32_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_BACKEND_KEY_DATA); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t)); kiwi_write32(&pos, pid); kiwi_write32(&pos, key); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_parameter_status(machine_msg_t *msg, char *key, int key_len, char *value, int value_len) { size_t size = sizeof(kiwi_header_t) + key_len + value_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_PARAMETER_STATUS); kiwi_write32(&pos, sizeof(uint32_t) + key_len + value_len); kiwi_write(&pos, key, key_len); kiwi_write(&pos, value, value_len); return msg; } KIWI_API static inline machine_msg_t *kiwi_be_write_ready(machine_msg_t *msg, uint8_t status) { size_t size = sizeof(kiwi_header_t) + sizeof(uint8_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_READY_FOR_QUERY); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint8_t)); kiwi_write8(&pos, status); return msg; } KIWI_API static inline int kiwi_be_format_ready(char *out, size_t max, uint8_t status) { if (5 > max) { return -1; } char *pos = out; kiwi_write8(&pos, KIWI_BE_READY_FOR_QUERY); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint8_t)); kiwi_write8(&pos, status); return pos - out; } KIWI_API static inline int kiwi_be_write_complete(machine_msg_t *msg, char *message, int len) { size_t size = sizeof(kiwi_header_t) + len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } machine_msg_t *local_msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(local_msg == NULL)) { return -1; } char *pos; pos = (char *)machine_msg_data(local_msg) + offset; kiwi_write8(&pos, KIWI_BE_COMMAND_COMPLETE); kiwi_write32(&pos, sizeof(uint32_t) + len); kiwi_write(&pos, message, len); if (msg == NULL) { machine_msg_free(local_msg); } return 0; } KIWI_API static inline machine_msg_t * kiwi_be_write_command_complete(machine_msg_t *msg, const char *message) { int len = strlen(message) + 1; size_t size = sizeof(kiwi_header_t) + len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_COMMAND_COMPLETE); kiwi_write32(&pos, sizeof(uint32_t) + len); kiwi_write(&pos, message, len); return msg; } KIWI_API static inline int kiwi_be_format_command_complete(char *out, size_t max, const char *command) { int len = strlen(command) + 1; size_t size = sizeof(kiwi_header_t) + len; if (size > max) { return -1; } char *pos = out; kiwi_write8(&pos, KIWI_BE_COMMAND_COMPLETE); kiwi_write32(&pos, sizeof(uint32_t) + len); kiwi_write(&pos, command, len); return pos - out; } KIWI_API static inline machine_msg_t * kiwi_be_write_empty_query(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_EMPTY_QUERY_RESPONSE); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_parse_complete(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_PARSE_COMPLETE); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_bind_complete(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_BIND_COMPLETE); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_close_complete(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_CLOSE_COMPLETE); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_portal_suspended(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_PORTAL_SUSPENDED); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t *kiwi_be_write_no_data(machine_msg_t *msg) { size_t size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_NO_DATA); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline int kiwi_be_format_notice(char *out, size_t max, char type, const char *text) { char notice[] = "NOTICE\0s"; int tlen = strlen(text); size_t size = sizeof(kiwi_header_t) + 1 + sizeof(notice) + 1 + tlen + 1 + 1; if (size > max) { return -1; } char *pos = out; kiwi_write8(&pos, KIWI_BE_NOTICE_RESPONSE); kiwi_write32(&pos, size - 1); kiwi_write8(&pos, 'S'); kiwi_write(&pos, notice, sizeof(notice)); kiwi_write8(&pos, type); kiwi_write(&pos, text, tlen + 1); kiwi_write8(&pos, 0 /* last msg in this Notice */); return pos - out; } KIWI_API static inline machine_msg_t * kiwi_be_write_notice(machine_msg_t *msg, char type, const char *text) { char notice[] = "NOTICE"; int tlen = strlen(text); size_t size = sizeof(kiwi_header_t) + 1 + sizeof(notice) + 1 + tlen + 1 + 1; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_NOTICE_RESPONSE); kiwi_write32(&pos, size - 1); kiwi_write8(&pos, 'S'); kiwi_write(&pos, notice, sizeof(notice)); kiwi_write8(&pos, type); kiwi_write(&pos, text, tlen + 1); kiwi_write8(&pos, 0 /* last msg in this Notice */); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_row_description(machine_msg_t *msg, int *begin_offset) { size_t size = sizeof(kiwi_header_t) + sizeof(uint16_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } *begin_offset = offset; msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_ROW_DESCRIPTION); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint16_t)); kiwi_write16(&pos, 0); return msg; } KIWI_API static inline machine_msg_t * kiwi_be_write_compression_ack(machine_msg_t *msg, char compression_algorithm) { size_t size = sizeof(kiwi_header_t) + sizeof(char); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_COMPRESSION); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(char)); kiwi_write8(&pos, compression_algorithm); return msg; } KIWI_API static inline int kiwi_be_write_row_description_add( machine_msg_t *msg, int begin_offset, char *name, int name_len, int32_t table_id, int16_t attrnum, int32_t type_id, int16_t type_size, int32_t type_modifier, int32_t format_code) { assert(msg); size_t size = name_len + 1 + sizeof(uint32_t) /* table_id */ + sizeof(uint16_t) /* attrnum */ + sizeof(uint32_t) /* type_id */ + sizeof(uint16_t) /* type_size */ + sizeof(uint32_t) /* type_modifier */ + sizeof(uint16_t) /* format_code */; size_t size_written = machine_msg_size(msg); int rc; rc = machine_msg_write(msg, NULL, size); if (kiwi_unlikely(rc == -1)) { return -1; } char *pos; pos = (char *)machine_msg_data(msg) + size_written; kiwi_write(&pos, name, name_len); kiwi_write8(&pos, 0); kiwi_write32(&pos, table_id); kiwi_write16(&pos, attrnum); kiwi_write32(&pos, type_id); kiwi_write16(&pos, type_size); kiwi_write32(&pos, type_modifier); kiwi_write16(&pos, format_code); kiwi_header_t *header; header = (kiwi_header_t *)((char *)machine_msg_data(msg) + begin_offset); uint32_t pos_size = sizeof(uint32_t) + sizeof(uint16_t); pos = (char *)&header->len; uint32_t total_size; uint16_t count; kiwi_read32(&total_size, &pos, &pos_size); kiwi_read16(&count, &pos, &pos_size); total_size += size; count++; kiwi_write32to((char *)&header->len, total_size); kiwi_write16to((char *)&header->len + sizeof(uint32_t), count); return 0; } KIWI_API static inline machine_msg_t * kiwi_be_write_row_descriptionf(machine_msg_t *msg, char *fmt, ...) { int is_msg_allocated = msg == NULL; int begin_offset; msg = kiwi_be_write_row_description(msg, &begin_offset); if (kiwi_unlikely(msg == NULL)) { return NULL; } va_list args; va_start(args, fmt); while (*fmt) { char *name = va_arg(args, char *); size_t name_len = strlen(name); int rc = 0; switch (*fmt) { case 's': rc = kiwi_be_write_row_description_add( msg, begin_offset, name, name_len, 0, 0, 25 /* TEXTOID */, -1, 0, 0); break; case 'd': rc = kiwi_be_write_row_description_add( msg, begin_offset, name, name_len, 0, 0, 23 /* INT4OID */, 4, 0, 0); break; case 'l': rc = kiwi_be_write_row_description_add( msg, begin_offset, name, name_len, 0, 0, 20 /* INT8OID */, 8, 0, 0); break; case 'b': rc = kiwi_be_write_row_description_add( msg, begin_offset, name, name_len, 0, 0, 16 /* BOOLOID */, 1, 0, 0); break; case 'f': rc = kiwi_be_write_row_description_add( msg, begin_offset, name, name_len, 0, 0, 700 /* FLOAT4OID */, 4, 0, 0); break; } if (rc == -1) { if (is_msg_allocated) { machine_msg_free(msg); } va_end(args); return NULL; } fmt++; } va_end(args); return msg; } KIWI_API static inline machine_msg_t *kiwi_be_write_data_row(machine_msg_t *msg, int *begin_offset) { size_t size = sizeof(kiwi_header_t) + sizeof(uint16_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } *begin_offset = offset; msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_BE_DATA_ROW); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(uint16_t)); kiwi_write16(&pos, 0); return msg; } KIWI_API static inline int kiwi_be_write_data_row_add(machine_msg_t *msg, int begin_offset, const char *data, int32_t len) { assert(msg); int is_null = len == -1; size_t size = sizeof(uint32_t) + (is_null ? 0 : len); size_t size_written = machine_msg_size(msg); int rc; rc = machine_msg_write(msg, NULL, size); if (kiwi_unlikely(rc == -1)) { return -1; } char *pos; pos = (char *)machine_msg_data(msg) + size_written; kiwi_write32(&pos, len); if (!is_null) { kiwi_write(&pos, data, len); } kiwi_header_t *header; header = (kiwi_header_t *)((char *)machine_msg_data(msg) + begin_offset); uint32_t pos_size = sizeof(uint32_t) + sizeof(uint16_t); pos = (char *)&header->len; uint32_t total_size; uint16_t count; kiwi_read32(&total_size, &pos, &pos_size); kiwi_read16(&count, &pos, &pos_size); total_size += size; count++; kiwi_write32to((char *)&header->len, total_size); kiwi_write16to((char *)&header->len + sizeof(uint32_t), count); return 0; } KIWI_API static inline machine_msg_t * kiwi_be_write_protocol_negotiation(machine_msg_t *msg, uint32_t proto_version, const char *unsupported_features[], size_t nfeatures) { int msgs_len = 0; for (size_t i = 0; i < nfeatures; ++i) { msgs_len += strlen(unsupported_features[i]) + 1 /* NULL-bytex */; } size_t size = sizeof(kiwi_header_t) + sizeof(uint32_t) /* version */ + sizeof(uint32_t) /* number of not supported features */ + msgs_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } machine_msg_t *local_msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(local_msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(local_msg) + offset; kiwi_write8(&pos, KIWI_BE_NEGOTIATE_PROTOCOL_VERSION); kiwi_write32(&pos, size - 1); kiwi_write32(&pos, proto_version); kiwi_write32(&pos, (uint32_t)nfeatures); for (size_t i = 0; i < nfeatures; ++i) { kiwi_write(&pos, unsupported_features[i], strlen(unsupported_features[i]) + 1); } return local_msg; } odyssey-1.6.0-rc1/sources/include/kiwi/error_codes.h000066400000000000000000000317431526064202200224370ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ /* Based on PostgreSQL 9.6 error codes src/backend/errorcodes.txt */ /* Class 00 - Successful Completion */ #define KIWI_SUCCESSFUL_COMPLETION "00000" /* Class 01 - Warning */ #define KIWI_WARNING "01000" #define KIWI_WARNING_DYNAMIC_RESULT_SETS_RETURNED "0100C" #define KIWI_WARNING_IMPLICIT_ZERO_BIT_PADDING "01008" #define KIWI_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION "01003" #define KIWI_WARNING_PRIVILEGE_NOT_GRANTED "01007" #define KIWI_WARNING_PRIVILEGE_NOT_REVOKED "01006" #define KIWI_WARNING_STRING_DATA_RIGHT_TRUNCATION "01004" #define KIWI_WARNING_DEPRECATED_FEATURE "01P01" /* Class 02 - No Data "this is also a warning class per the SQL standard" */ #define KIWI_NO_DATA "02000" #define KIWI_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED "02001" /* Class 03 - SQL Statement Not Yet Complete */ #define KIWI_SQL_STATEMENT_NOT_YET_COMPLETE "03000" /* Class 08 - Connection Exception */ #define KIWI_CONNECTION_EXCEPTION "08000" #define KIWI_CONNECTION_DOES_NOT_EXIST "08003" #define KIWI_CONNECTION_FAILURE "08006" #define KIWI_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION "08001" #define KIWI_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION "08004" #define KIWI_TRANSACTION_RESOLUTION_UNKNOWN "08007" #define KIWI_PROTOCOL_VIOLATION "08P01" /* Class 09 - Triggered Action Exception */ #define KIWI_TRIGGERED_ACTION_EXCEPTION "09000" /* Class 0A - Feature Not Supported */ #define KIWI_FEATURE_NOT_SUPPORTED "0A000" /* Class 0B - Invalid Transaction Initiation */ #define KIWI_INVALID_TRANSACTION_INITIATION "0B000" /* Class 0F - Locator Exception */ #define KIWI_LOCATOR_EXCEPTION "0F000" #define KIWI_L_E_INVALID_SPECIFICATION "0F001" /* Class 0L - Invalid Grantor */ #define KIWI_INVALID_GRANTOR "0L000" #define KIWI_INVALID_GRANT_OPERATION "0LP01" /* Class 0P - Invalid Role Specification */ #define KIWI_INVALID_ROLE_SPECIFICATION "0P000" /* Class 0Z - Diagnostics Exception */ #define KIWI_DIAGNOSTICS_EXCEPTION "0Z000" #define KIWI_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER "0Z002" /* Class 20 - Case Not Found */ #define KIWI_CASE_NOT_FOUND "20000" /* Class 21 - Cardinality Violation */ #define KIWI_CARDINALITY_VIOLATION "21000" /* Class 22 - Data Exception */ #define KIWI_DATA_EXCEPTION "22000" #define KIWI_ARRAY_ELEMENT_ERROR "2202E" #define KIWI_ARRAY_SUBSCRIPT_ERROR "2202E" #define KIWI_CHARACTER_NOT_IN_REPERTOIRE "22021" #define KIWI_DATETIME_FIELD_OVERFLOW "22008" #define KIWI_DATETIME_VALUE_OUT_OF_RANGE "22008" #define KIWI_DIVISION_BY_ZERO "22012" #define KIWI_ERROR_IN_ASSIGNMENT "22005" #define KIWI_ESCAPE_CHARACTER_CONFLICT "2200B" #define KIWI_INDICATOR_OVERFLOW "22022" #define KIWI_INTERVAL_FIELD_OVERFLOW "22015" #define KIWI_INVALID_ARGUMENT_FOR_LOG "2201E" #define KIWI_INVALID_ARGUMENT_FOR_NTILE "22014" #define KIWI_INVALID_ARGUMENT_FOR_NTH_VALUE "22016" #define KIWI_INVALID_ARGUMENT_FOR_POWER_FUNCTION "2201F" #define KIWI_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION "2201G" #define KIWI_INVALID_CHARACTER_VALUE_FOR_CAST "22018" #define KIWI_INVALID_DATETIME_FORMAT "22007" #define KIWI_INVALID_ESCAPE_CHARACTER "22019" #define KIWI_INVALID_ESCAPE_OCTET "2200D" #define KIWI_INVALID_ESCAPE_SEQUENCE "22025" #define KIWI_NONSTANDARD_USE_OF_ESCAPE_CHARACTER "22P06" #define KIWI_INVALID_INDICATOR_PARAMETER_VALUE "22010" #define KIWI_INVALID_PARAMETER_VALUE "22023" #define KIWI_INVALID_REGULAR_EXPRESSION "2201B" #define KIWI_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE "2201W" #define KIWI_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE "2201X" #define KIWI_INVALID_TABLESAMPLE_ARGUMENT "2202H" #define KIWI_INVALID_TABLESAMPLE_REPEAT "2202G" #define KIWI_INVALID_TIME_ZONE_DISPLACEMENT_VALUE "22009" #define KIWI_INVALID_USE_OF_ESCAPE_CHARACTER "2200C" #define KIWI_MOST_SPECIFIC_TYPE_MISMATCH "2200G" #define KIWI_NULL_VALUE_NOT_ALLOWED "22004" #define KIWI_NULL_VALUE_NO_INDICATOR_PARAMETER "22002" #define KIWI_NUMERIC_VALUE_OUT_OF_RANGE "22003" #define KIWI_STRING_DATA_LENGTH_MISMATCH "22026" #define KIWI_STRING_DATA_RIGHT_TRUNCATION "22001" #define KIWI_SUBSTRING_ERROR "22011" #define KIWI_TRIM_ERROR "22027" #define KIWI_UNTERMINATED_C_STRING "22024" #define KIWI_ZERO_LENGTH_CHARACTER_STRING "2200F" #define KIWI_FLOATING_POINT_EXCEPTION "22P01" #define KIWI_INVALID_TEXT_REPRESENTATION "22P02" #define KIWI_INVALID_BINARY_REPRESENTATION "22P03" #define KIWI_BAD_COPY_FILE_FORMAT "22P04" #define KIWI_UNTRANSLATABLE_CHARACTER "22P05" #define KIWI_NOT_AN_XML_DOCUMENT "2200L" #define KIWI_INVALID_XML_DOCUMENT "2200M" #define KIWI_INVALID_XML_CONTENT "2200N" #define KIWI_INVALID_XML_COMMENT "2200S" #define KIWI_INVALID_XML_PROCESSING_INSTRUCTION "2200T" /* Class 23 - Integrity Constraint Violation */ #define KIWI_INTEGRITY_CONSTRAINT_VIOLATION "23000" #define KIWI_RESTRICT_VIOLATION "23001" #define KIWI_NOT_NULL_VIOLATION "23502" #define KIWI_FOREIGN_KEY_VIOLATION "23503" #define KIWI_UNIQUE_VIOLATION "23505" #define KIWI_CHECK_VIOLATION "23514" #define KIWI_EXCLUSION_VIOLATION "23P01" /* Class 24 - Invalid Cursor State */ #define KIWI_INVALID_CURSOR_STATE "24000" /* Class 25 - Invalid Transaction State */ #define KIWI_INVALID_TRANSACTION_STATE "25000" #define KIWI_ACTIVE_SQL_TRANSACTION "25001" #define KIWI_BRANCH_TRANSACTION_ALREADY_ACTIVE "25002" #define KIWI_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL "25008" #define KIWI_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION "25003" #define KIWI_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION "25004" #define KIWI_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION "25005" #define KIWI_READ_ONLY_SQL_TRANSACTION "25006" #define KIWI_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED "25007" #define KIWI_NO_ACTIVE_SQL_TRANSACTION "25P01" #define KIWI_IN_FAILED_SQL_TRANSACTION "25P02" #define KIWI_IDLE_IN_TRANSACTION_SESSION_TIMEOUT "25P03" /* Class 26 - Invalid SQL Statement Name */ #define KIWI_INVALID_SQL_STATEMENT_NAME "26000" /* Class 27 - Triggered Data Change Violation */ #define KIWI_TRIGGERED_DATA_CHANGE_VIOLATION "27000" /* Class 28 - Invalid Authorization Specification */ #define KIWI_INVALID_AUTHORIZATION_SPECIFICATION "28000" #define KIWI_INVALID_PASSWORD "28P01" /* Class 2B - Dependent Privilege Descriptors Still Exist */ #define KIWI_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST "2B000" #define KIWI_DEPENDENT_OBJECTS_STILL_EXIST "2BP01" /* Class 2D - Invalid Transaction Termination */ #define KIWI_INVALID_TRANSACTION_TERMINATION "2D000" /* Class 2F - SQL Routine Exception */ #define KIWI_SQL_ROUTINE_EXCEPTION "2F000" #define KIWI_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT "2F005" #define KIWI_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED "2F002" #define KIWI_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED "2F003" #define KIWI_S_R_E_READING_SQL_DATA_NOT_PERMITTED "2F004" /* Class 34 - Invalid Cursor Name */ #define KIWI_INVALID_CURSOR_NAME "34000" /* Class 38 - External Routine Exception */ #define KIWI_EXTERNAL_ROUTINE_EXCEPTION "38000" #define KIWI_E_R_E_CONTAINING_SQL_NOT_PERMITTED "38001" #define KIWI_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED "38002" #define KIWI_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED "38003" #define KIWI_E_R_E_READING_SQL_DATA_NOT_PERMITTED "38004" /* Class 39 - External Routine Invocation Exception */ #define KIWI_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION "39000" #define KIWI_E_R_I_E_INVALID_SQLSTATE_RETURNED "39001" #define KIWI_E_R_I_E_NULL_VALUE_NOT_ALLOWED "39004" #define KIWI_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED "39P01" #define KIWI_E_R_I_E_SRF_PROTOCOL_VIOLATED "39P02" #define KIWI_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED "39P03" /* Class 3B - Savepoint Exception */ #define KIWI_SAVEPOINT_EXCEPTION "3B000" #define KIWI_S_E_INVALID_SPECIFICATION "3B001" /* Class 3D - Invalid Catalog Name */ #define KIWI_INVALID_CATALOG_NAME "3D000" /* Class 3F - Invalid Schema Name */ #define KIWI_INVALID_SCHEMA_NAME "3F000" /* Class 40 - Transaction Rollback */ #define KIWI_TRANSACTION_ROLLBACK "40000" #define KIWI_T_R_INTEGRITY_CONSTRAINT_VIOLATION "40002" #define KIWI_T_R_SERIALIZATION_FAILURE "40001" #define KIWI_T_R_STATEMENT_COMPLETION_UNKNOWN "40003" #define KIWI_T_R_DEADLOCK_DETECTED "40P01" /* Class 42 - Syntax Error or Access Rule Violation */ #define KIWI_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION "42000" #define KIWI_SYNTAX_ERROR "42601" #define KIWI_INSUFFICIENT_PRIVILEGE "42501" #define KIWI_CANNOT_COERCE "42846" #define KIWI_GROUPING_ERROR "42803" #define KIWI_WINDOWING_ERROR "42P20" #define KIWI_INVALID_RECURSION "42P19" #define KIWI_INVALID_FOREIGN_KEY "42830" #define KIWI_INVALID_NAME "42602" #define KIWI_NAME_TOO_LONG "42622" #define KIWI_RESERVED_NAME "42939" #define KIWI_DATATYPE_MISMATCH "42804" #define KIWI_INDETERMINATE_DATATYPE "42P18" #define KIWI_COLLATION_MISMATCH "42P21" #define KIWI_INDETERMINATE_COLLATION "42P22" #define KIWI_WRONG_OBJECT_TYPE "42809" #define KIWI_UNDEFINED_COLUMN "42703" #define KIWI_UNDEFINED_CURSOR "34000" #define KIWI_UNDEFINED_DATABASE "3D000" #define KIWI_UNDEFINED_FUNCTION "42883" #define KIWI_UNDEFINED_PSTATEMENT "26000" #define KIWI_UNDEFINED_SCHEMA "3F000" #define KIWI_UNDEFINED_TABLE "42P01" #define KIWI_UNDEFINED_PARAMETER "42P02" #define KIWI_UNDEFINED_OBJECT "42704" #define KIWI_DUPLICATE_COLUMN "42701" #define KIWI_DUPLICATE_CURSOR "42P03" #define KIWI_DUPLICATE_DATABASE "42P04" #define KIWI_DUPLICATE_FUNCTION "42723" #define KIWI_DUPLICATE_PSTATEMENT "42P05" #define KIWI_DUPLICATE_SCHEMA "42P06" #define KIWI_DUPLICATE_TABLE "42P07" #define KIWI_DUPLICATE_ALIAS "42712" #define KIWI_DUPLICATE_OBJECT "42710" #define KIWI_AMBIGUOUS_COLUMN "42702" #define KIWI_AMBIGUOUS_FUNCTION "42725" #define KIWI_AMBIGUOUS_PARAMETER "42P08" #define KIWI_AMBIGUOUS_ALIAS "42P09" #define KIWI_INVALID_COLUMN_REFERENCE "42P10" #define KIWI_INVALID_COLUMN_DEFINITION "42611" #define KIWI_INVALID_CURSOR_DEFINITION "42P11" #define KIWI_INVALID_DATABASE_DEFINITION "42P12" #define KIWI_INVALID_FUNCTION_DEFINITION "42P13" #define KIWI_INVALID_PSTATEMENT_DEFINITION "42P14" #define KIWI_INVALID_SCHEMA_DEFINITION "42P15" #define KIWI_INVALID_TABLE_DEFINITION "42P16" #define KIWI_INVALID_OBJECT_DEFINITION "42P17" /* Class 44 - WITH CHECK OPTION Violation */ #define KIWI_WITH_CHECK_OPTION_VIOLATION "44000" /* Class 53 - Insufficient Resources */ #define KIWI_INSUFFICIENT_RESOURCES "53000" #define KIWI_DISK_FULL "53100" #define KIWI_OUT_OF_MEMORY "53200" #define KIWI_TOO_MANY_CONNECTIONS "53300" #define KIWI_CONFIGURATION_LIMIT_EXCEEDED "53400" /* Class 54 - Program Limit Exceeded */ #define KIWI_PROGRAM_LIMIT_EXCEEDED "54000" #define KIWI_STATEMENT_TOO_COMPLEX "54001" #define KIWI_TOO_MANY_COLUMNS "54011" #define KIWI_TOO_MANY_ARGUMENTS "54023" /* Class 55 - Object Not In Prerequisite State */ #define KIWI_OBJECT_NOT_IN_PREREQUISITE_STATE "55000" #define KIWI_OBJECT_IN_USE "55006" #define KIWI_CANT_CHANGE_RUNTIME_PARAM "55P02" #define KIWI_LOCK_NOT_AVAILABLE "55P03" /* Class 57 - Operator Intervention */ #define KIWI_OPERATOR_INTERVENTION "57000" #define KIWI_QUERY_CANCELED "57014" #define KIWI_ADMIN_SHUTDOWN "57P01" #define KIWI_CRASH_SHUTDOWN "57P02" #define KIWI_CANNOT_CONNECT_NOW "57P03" #define KIWI_DATABASE_DROPPED "57P04" /* Class 58 - System Error "errors external to PostgreSQL itself" */ #define KIWI_SYSTEM_ERROR "58000" #define KIWI_IO_ERROR "58030" #define KIWI_UNDEFINED_FILE "58P01" #define KIWI_DUPLICATE_FILE "58P02" /* Class 72 - Snapshot Failure */ #define KIWI_SNAPSHOT_TOO_OLD "72000" /* Class F0 - Configuration File Error */ #define KIWI_CONFIG_FILE_ERROR "F0000" #define KIWI_LOCK_FILE_EXISTS "F0001" /* Class HV - Foreign Data Wrapper Error "SQL/MED" */ #define KIWI_FDW_ERROR "HV000" #define KIWI_FDW_COLUMN_NAME_NOT_FOUND "HV005" #define KIWI_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED "HV002" #define KIWI_FDW_FUNCTION_SEQUENCE_ERROR "HV010" #define KIWI_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION "HV021" #define KIWI_FDW_INVALID_ATTRIBUTE_VALUE "HV024" #define KIWI_FDW_INVALID_COLUMN_NAME "HV007" #define KIWI_FDW_INVALID_COLUMN_NUMBER "HV008" #define KIWI_FDW_INVALID_DATA_TYPE "HV004" #define KIWI_FDW_INVALID_DATA_TYPE_DESCRIPTORS "HV006" #define KIWI_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER "HV091" #define KIWI_FDW_INVALID_HANDLE "HV00B" #define KIWI_FDW_INVALID_OPTION_INDEX "HV00C" #define KIWI_FDW_INVALID_OPTION_NAME "HV00D" #define KIWI_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH "HV090" #define KIWI_FDW_INVALID_STRING_FORMAT "HV00A" #define KIWI_FDW_INVALID_USE_OF_NULL_POINTER "HV009" #define KIWI_FDW_TOO_MANY_HANDLES "HV014" #define KIWI_FDW_OUT_OF_MEMORY "HV001" #define KIWI_FDW_NO_SCHEMAS "HV00P" #define KIWI_FDW_OPTION_NAME_NOT_FOUND "HV00J" #define KIWI_FDW_REPLY_HANDLE "HV00K" #define KIWI_FDW_SCHEMA_NOT_FOUND "HV00Q" #define KIWI_FDW_TABLE_NOT_FOUND "HV00R" #define KIWI_FDW_UNABLE_TO_CREATE_EXECUTION "HV00L" #define KIWI_FDW_UNABLE_TO_CREATE_REPLY "HV00M" #define KIWI_FDW_UNABLE_TO_ESTABLISH_CONNECTION "HV00N" /* Class P0 - PL/pgSQL Error */ #define KIWI_PLPGSQL_ERROR "P0000" #define KIWI_RAISE_EXCEPTION "P0001" #define KIWI_NO_DATA_FOUND "P0002" #define KIWI_TOO_MANY_ROWS "P0003" #define KIWI_ASSERT_FAILURE "P0004" /* Class XX - Internal Error */ #define KIWI_INTERNAL_ERROR "XX000" #define KIWI_DATA_CORRUPTED "XX001" #define KIWI_INDEX_CORRUPTED "XX002" odyssey-1.6.0-rc1/sources/include/kiwi/fe_read.h000066400000000000000000000125011526064202200215050ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_fe_error kiwi_fe_error_t; struct kiwi_fe_error { char *severity; char *code; char *message; char *detail; char *hint; }; KIWI_API static inline int kiwi_fe_read_ready(char *data, uint32_t size, int *status) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_BE_READY_FOR_QUERY || len != 1)) { return -1; } *status = *kiwi_header_data(header); return 0; } KIWI_API static inline int kiwi_fe_read_key(char *data, uint32_t size, kiwi_key_t *key) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_BE_BACKEND_KEY_DATA || len != 8)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); rc = kiwi_read32(&key->key_pid, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } rc = kiwi_read32(&key->key, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } return 0; } KIWI_API static inline int kiwi_fe_read_auth(char *data, uint32_t size, uint32_t *type, char salt[4], char **auth_data, size_t *auth_data_size) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_BE_AUTHENTICATION)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); rc = kiwi_read32(type, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } switch (*type) { /* AuthenticationOk */ case 0: return 0; /* AuthenticationCleartextPassword */ case 3: return 0; /* AuthenticationMD5Password */ case 5: if (pos_size != 4) { return -1; } memcpy(salt, pos, 4); return 0; /* AuthenticationSASL */ case 10: /* PostgreSQL sends a list of SASL mechanisms as null-terminated * strings. When TLS is enabled, PostgreSQL sends the list: * ["SCRAM-SHA-256-PLUS", "SCRAM-SHA-256", ""]. * We need to iterate through the list to find SCRAM-SHA-256. */ { char *mechanism = pos; int found = 0; size_t offset = 0; while (offset < pos_size && mechanism[0] != '\0') { /* Find the end of this mechanism string safely */ size_t len = 0; while (offset + len < pos_size && mechanism[len] != '\0') { len++; } /* Check if we found null terminator within bounds */ if (offset + len >= pos_size) { break; } if (strcmp(mechanism, "SCRAM-SHA-256") == 0) { found = 1; break; } offset += len + 1; mechanism += len + 1; } if (!found) { return -1; } } return 0; /* AuthenticationSASLContinue */ case 11: /* AuthenticationSASLFinal */ case 12: if (auth_data != NULL) { *auth_data = pos; } if (auth_data_size != NULL) { *auth_data_size = pos_size; } return 0; } /* unsupported */ return -1; } KIWI_API static inline int kiwi_fe_read_parameter(char *data, uint32_t size, char **name, uint32_t *name_len, char **value, uint32_t *value_len) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_BE_PARAMETER_STATUS)) { return -1; } uint32_t pos_size = len; char *pos = kiwi_header_data(header); /* name */ *name = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *name_len = pos - *name; /* value */ *value = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } *value_len = pos - *value; return 0; } KIWI_API static inline int kiwi_fe_read_error(char *data, uint32_t size, kiwi_fe_error_t *error) { kiwi_header_t *header = (kiwi_header_t *)data; uint32_t len; int rc = kiwi_read(&len, &data, &size); if (kiwi_unlikely(rc != 0)) { return -1; } if (kiwi_unlikely(header->type != KIWI_BE_ERROR_RESPONSE)) { return -1; } memset(error, 0, sizeof(*error)); uint32_t pos_size = len; char *pos = kiwi_header_data(header); for (;;) { char type; int rc; rc = kiwi_read8(&type, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } switch (type) { /* severity */ case 'S': error->severity = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* sqlstate */ case 'C': error->code = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* message */ case 'M': error->message = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* detail */ case 'D': error->detail = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* hint */ case 'H': error->hint = pos; rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* end */ case 0: return 0; default: rc = kiwi_readsz(&pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; } } return 0; } odyssey-1.6.0-rc1/sources/include/kiwi/fe_write.h000066400000000000000000000311011526064202200217210ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_fe_arg kiwi_fe_arg_t; struct kiwi_fe_arg { char *name; int len; }; KIWI_API static inline machine_msg_t * kiwi_fe_write_startup_message(machine_msg_t *msg, int argc, kiwi_fe_arg_t *argv) { int size = sizeof(uint32_t) + /* len */ sizeof(uint32_t) + /* version */ sizeof(uint8_t); /* last '\0' */ int i = 0; for (; i < argc; i++) { size += argv[i].len; } int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; /* len */ kiwi_write32(&pos, size); /* version */ kiwi_write32(&pos, 196608); /* arguments */ for (i = 0; i < argc; i++) { kiwi_write(&pos, argv[i].name, argv[i].len); } /* eof */ kiwi_write8(&pos, 0); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_cancel(machine_msg_t *msg, uint32_t pid, uint32_t key) { int size = sizeof(uint32_t) + /* len */ sizeof(uint32_t) + /* special */ sizeof(uint32_t) + /* pid */ sizeof(uint32_t); /* key */ int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; /* len */ kiwi_write32(&pos, size); /* special */ kiwi_write32(&pos, 80877102); /* pid */ kiwi_write32(&pos, pid); /* key */ kiwi_write32(&pos, key); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_ssl_request(machine_msg_t *msg) { int size = sizeof(uint32_t) + /* len */ sizeof(uint32_t); /* special */ int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; /* len */ kiwi_write32(&pos, size); /* special */ kiwi_write32(&pos, 80877103); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_terminate(machine_msg_t *msg) { int size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_TERMINATE); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_write_flush(machine_msg_t *msg) { int size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_FLUSH); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_password(machine_msg_t *msg, char *password, int len) { int size = sizeof(kiwi_header_t) + len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_PASSWORD_MESSAGE); kiwi_write32(&pos, sizeof(uint32_t) + len); kiwi_write(&pos, password, len); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_query(machine_msg_t *msg, const char *query, int len) { int size = sizeof(kiwi_header_t) + len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_QUERY); kiwi_write32(&pos, sizeof(uint32_t) + len); kiwi_write(&pos, query, len); return msg; } /* join strings from args at one query */ KIWI_API static inline machine_msg_t * kiwi_fe_write_query_join(machine_msg_t *msg, ...) { int len = 0; const char *next; va_list args; va_start(args, msg); next = va_arg(args, const char *); len = 0; while (next != NULL) { len += strlen(next); next = va_arg(args, const char *); } va_end(args); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance( msg, sizeof(kiwi_header_t) + len + 1 /* zero-termination */); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_QUERY); kiwi_write32(&pos, sizeof(uint32_t) + len + 1); va_start(args, msg); next = va_arg(args, const char *); while (next != NULL) { len = strlen(next); kiwi_write(&pos, next, len); next = va_arg(args, const char *); } va_end(args); kiwi_write(&pos, "\0", 1); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_parse_description(machine_msg_t *msg, const char *operator_name, int operator_len, const char *description, int description_len) { size_t payload_size = operator_len + description_len; uint32_t size = sizeof(kiwi_header_t) + payload_size; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_PARSE); kiwi_write32(&pos, sizeof(uint32_t) + payload_size); kiwi_write(&pos, operator_name, operator_len); kiwi_write(&pos, description, description_len); return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_write_close(machine_msg_t *msg, char type, char *operator_name, int operator_len) { size_t payload_size = sizeof(char) + operator_len; uint32_t size = sizeof(kiwi_header_t) + payload_size; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_CLOSE); kiwi_write32(&pos, sizeof(uint32_t) + payload_size); kiwi_write8(&pos, type); kiwi_write(&pos, operator_name, operator_len); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_parse(machine_msg_t *msg, char *operator_name, int operator_len, const char *query, int query_len, uint16_t typec, int *typev) { size_t payload_size = operator_len + query_len + sizeof(uint16_t) + typec * sizeof(uint32_t); uint32_t size = sizeof(kiwi_header_t) + payload_size; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_PARSE); kiwi_write32(&pos, sizeof(uint32_t) + payload_size); kiwi_write(&pos, operator_name, operator_len); kiwi_write(&pos, query, query_len); kiwi_write16(&pos, typec); int i = 0; for (; i < typec; i++) { kiwi_write32(&pos, typev[i]); } return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_bind(machine_msg_t *msg, char *portal_name, int portal_len, char *operator_name, int operator_len, int argc_call_types, int call_types[], int argc_result_types, int result_types[], int argc, int *argv_len, char **argv) { size_t payload_size = portal_len + operator_len + sizeof(uint16_t) + /* argc_call_types */ sizeof(uint16_t) * argc_call_types + /* call_types */ sizeof(uint16_t) + /* argc_result_types */ sizeof(uint16_t) * argc_result_types + /* result_types */ sizeof(uint16_t); /* argc */ int i = 0; for (; i < argc; i++) { payload_size += sizeof(uint32_t); if (argv_len[i] == -1) { continue; } payload_size += argv_len[i]; } int size = sizeof(kiwi_header_t) + payload_size; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_BIND); kiwi_write32(&pos, sizeof(uint32_t) + payload_size); kiwi_write(&pos, portal_name, portal_len); kiwi_write(&pos, operator_name, operator_len); kiwi_write16(&pos, argc_call_types); for (i = 0; i < argc_call_types; i++) { kiwi_write16(&pos, call_types[i]); } kiwi_write16(&pos, argc); for (i = 0; i < argc; i++) { kiwi_write32(&pos, argv_len[i]); if (argv_len[i] == -1) { continue; } kiwi_write(&pos, argv[i], argv_len[i]); } kiwi_write16(&pos, argc_result_types); for (i = 0; i < argc_result_types; i++) { kiwi_write16(&pos, result_types[i]); } return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_write_describe(machine_msg_t *msg, uint8_t type, const char *name, int name_len) { int size = sizeof(kiwi_header_t) + sizeof(type) + name_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_DESCRIBE); kiwi_write32(&pos, sizeof(uint32_t) + sizeof(type) + name_len); kiwi_write8(&pos, type); kiwi_write(&pos, name, name_len); return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_write_execute(machine_msg_t *msg, char *portal, int portal_len, uint32_t limit) { int size = sizeof(kiwi_header_t) + portal_len + sizeof(limit); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_EXECUTE); kiwi_write32(&pos, sizeof(uint32_t) + portal_len + sizeof(limit)); kiwi_write(&pos, portal, portal_len); kiwi_write32(&pos, limit); return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_write_sync(machine_msg_t *msg) { int size = sizeof(kiwi_header_t); int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_SYNC); kiwi_write32(&pos, sizeof(uint32_t)); return msg; } /* support for multi-param stmts */ KIWI_API static inline machine_msg_t * kiwi_fe_write_prep_stmt(machine_msg_t *msg, const char *query, char *param) { msg = kiwi_fe_write_parse(msg, "", 1, query, strlen(query) + 1, 0, NULL); msg = kiwi_fe_write_bind(msg, "", 1, "", 1, 0, NULL, 0, NULL, 1, (int[]){ strlen(param) }, (char *[]){ param }); msg = kiwi_fe_write_describe(msg, 'P', "", 1); msg = kiwi_fe_write_execute(msg, "", 1, 0); msg = kiwi_fe_write_sync(msg); return msg; } KIWI_API static inline machine_msg_t * kiwi_fe_write_authentication_sasl_initial(machine_msg_t *msg, char *mechanism, char *initial_response, int initial_response_len) { int mechanism_len = strlen(mechanism); int size = sizeof(kiwi_header_t) + mechanism_len + sizeof(uint8_t) + sizeof(initial_response_len) + initial_response_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_PASSWORD_MESSAGE); kiwi_write32(&pos, size - sizeof(uint8_t)); kiwi_write(&pos, mechanism, mechanism_len); kiwi_write8(&pos, 0); /* write mechanism as a string */ kiwi_write32(&pos, initial_response_len); kiwi_write(&pos, initial_response, initial_response_len); return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_write_authentication_scram_final( machine_msg_t *msg, char *final_message, int final_message_len) { int size = sizeof(kiwi_header_t) + final_message_len; int offset = 0; if (msg) { offset = machine_msg_size(msg); } msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write8(&pos, KIWI_FE_PASSWORD_MESSAGE); kiwi_write32(&pos, size - sizeof(uint8_t)); kiwi_write(&pos, final_message, final_message_len); return msg; } KIWI_API static inline machine_msg_t *kiwi_fe_copy_msg(machine_msg_t *msg, char *data, int sizes) { int size = sizes; int offset = 0; msg = machine_msg_create_or_advance(msg, size); if (kiwi_unlikely(msg == NULL)) { return NULL; } char *pos; pos = (char *)machine_msg_data(msg) + offset; kiwi_write(&pos, data, sizes); return msg; } odyssey-1.6.0-rc1/sources/include/kiwi/header.h000066400000000000000000000077261526064202200213650ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_header kiwi_header_t; typedef enum { KIWI_FE_TERMINATE = 'X', KIWI_FE_PASSWORD_MESSAGE = 'p', KIWI_FE_QUERY = 'Q', KIWI_FE_FUNCTION_CALL = 'F', KIWI_FE_PARSE = 'P', KIWI_FE_BIND = 'B', KIWI_FE_DESCRIBE = 'D', KIWI_FE_EXECUTE = 'E', KIWI_FE_SYNC = 'S', KIWI_FE_FLUSH = 'H', KIWI_FE_CLOSE = 'C', KIWI_FE_COPY_DATA = 'd', KIWI_FE_COPY_DONE = 'c', KIWI_FE_COPY_FAIL = 'f' } kiwi_fe_type_t; typedef enum { KIWI_FE_CLOSE_PREPARED_STATEMENT = 'S', KIWI_FE_CLOSE_PORTAL = 'P', } kiwi_fe_close_type_t; typedef enum { KIWI_FE_DESCRIBE_PREPARED_STATEMENT = 'S', KIWI_FE_DESCRIBE_PORTAL = 'P', } kiwi_fe_describe_type_t; typedef enum { KIWI_BE_AUTHENTICATION = 'R', KIWI_BE_BACKEND_KEY_DATA = 'K', KIWI_BE_PARSE_COMPLETE = '1', KIWI_BE_BIND_COMPLETE = '2', KIWI_BE_CLOSE_COMPLETE = '3', KIWI_BE_COMMAND_COMPLETE = 'C', KIWI_BE_COPY_IN_RESPONSE = 'G', KIWI_BE_COPY_OUT_RESPONSE = 'H', KIWI_BE_COPY_BOTH_RESPONSE = 'W', KIWI_BE_COPY_DATA = 'd', KIWI_BE_COPY_DONE = 'c', KIWI_BE_COPY_FAIL = 'f', KIWI_BE_DATA_ROW = 'D', KIWI_BE_EMPTY_QUERY_RESPONSE = 'I', KIWI_BE_ERROR_RESPONSE = 'E', KIWI_BE_FUNCTION_CALL_RESPONSE = 'V', KIWI_BE_NEGOTIATE_PROTOCOL_VERSION = 'v', KIWI_BE_NO_DATA = 'n', KIWI_BE_NOTICE_RESPONSE = 'N', KIWI_BE_NOTIFICATION_RESPONSE = 'A', KIWI_BE_PARAMETER_DESCRIPTION = 't', KIWI_BE_PARAMETER_STATUS = 'S', KIWI_BE_PORTAL_SUSPENDED = 's', KIWI_BE_READY_FOR_QUERY = 'Z', KIWI_BE_ROW_DESCRIPTION = 'T', KIWI_BE_COMPRESSION = 'z', } kiwi_be_type_t; struct kiwi_header { uint8_t type; uint32_t len; } __attribute__((packed)); static inline char *kiwi_header_data(kiwi_header_t *header) { return (char *)header + sizeof(kiwi_header_t); } static inline char *kiwi_fe_type_to_string(int type) { switch (type) { case KIWI_FE_TERMINATE: return "Terminate"; case KIWI_FE_PASSWORD_MESSAGE: return "PasswordMessage"; case KIWI_FE_QUERY: return "Query"; case KIWI_FE_FUNCTION_CALL: return "FunctionCall"; case KIWI_FE_PARSE: return "Parse"; case KIWI_FE_BIND: return "Bind"; case KIWI_FE_DESCRIBE: return "Describe"; case KIWI_FE_EXECUTE: return "Execute"; case KIWI_FE_SYNC: return "Sync"; case KIWI_FE_FLUSH: return "Flush"; case KIWI_FE_CLOSE: return "Close"; case KIWI_FE_COPY_DATA: return "CopyData"; case KIWI_FE_COPY_DONE: return "CopyDone"; case KIWI_FE_COPY_FAIL: return "CopyFail"; } return "Unknown"; } static inline char *kiwi_be_type_to_string(int type) { switch (type) { case KIWI_BE_AUTHENTICATION: return "Authentication"; case KIWI_BE_BACKEND_KEY_DATA: return "BackendKeyData"; case KIWI_BE_PARSE_COMPLETE: return "ParseComplete"; case KIWI_BE_BIND_COMPLETE: return "BindComplete"; case KIWI_BE_CLOSE_COMPLETE: return "CloseComplete"; case KIWI_BE_COMMAND_COMPLETE: return "CommandComplete"; case KIWI_BE_COPY_IN_RESPONSE: return "CopyInResponse"; case KIWI_BE_COPY_OUT_RESPONSE: return "CopyOutResponse"; case KIWI_BE_COPY_BOTH_RESPONSE: return "CopyBothResponse"; case KIWI_BE_COPY_DATA: return "CopyData"; case KIWI_BE_COPY_DONE: return "CopyDone"; case KIWI_BE_COPY_FAIL: return "CopyFail"; case KIWI_BE_DATA_ROW: return "DataRow"; case KIWI_BE_EMPTY_QUERY_RESPONSE: return "EmptyQueryResponse"; case KIWI_BE_ERROR_RESPONSE: return "ErrorResponse"; case KIWI_BE_FUNCTION_CALL_RESPONSE: return "FunctionCallResponse"; case KIWI_BE_NEGOTIATE_PROTOCOL_VERSION: return "NegotiateProtocolVersion"; case KIWI_BE_NO_DATA: return "NoData"; case KIWI_BE_NOTICE_RESPONSE: return "NoticeResponse"; case KIWI_BE_NOTIFICATION_RESPONSE: return "NotificationResponse"; case KIWI_BE_PARAMETER_DESCRIPTION: return "ParameterDescription"; case KIWI_BE_PARAMETER_STATUS: return "ParameterStatus"; case KIWI_BE_PORTAL_SUSPENDED: return "PortalSuspended"; case KIWI_BE_READY_FOR_QUERY: return "ReadyForQuery"; case KIWI_BE_ROW_DESCRIPTION: return "RowDescription"; } return "Unknown"; } odyssey-1.6.0-rc1/sources/include/kiwi/io.h000066400000000000000000000124341526064202200205340ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ static inline int kiwi_read8(char *out, char **pos, uint32_t *size) { if (kiwi_unlikely(*size < sizeof(uint8_t))) { return -1; } *out = *pos[0]; *size -= sizeof(uint8_t); *pos += sizeof(uint8_t); return 0; } static inline int kiwi_read16(uint16_t *out, char **pos, uint32_t *size) { if (kiwi_unlikely(*size < sizeof(uint16_t))) { return -1; } unsigned char *ptr = (unsigned char *)*pos; *out = ptr[0] << 8 | ptr[1]; *size -= sizeof(uint16_t); *pos += sizeof(uint16_t); return 0; } static inline int kiwi_read32(uint32_t *out, char **pos, uint32_t *size) { if (kiwi_unlikely(*size < sizeof(uint32_t))) { return -1; } unsigned char *ptr = (unsigned char *)*pos; *out = ((uint32_t)ptr[0]) << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; *size -= sizeof(uint32_t); *pos += sizeof(uint32_t); return 0; } static inline int kiwi_readsz(char **pos, uint32_t *size) { char *p = *pos; char *end = p + *size; while (p < end && *p) { p++; } if (kiwi_unlikely(p == end)) { return -1; } *size -= (uint32_t)(p - *pos) + 1; *pos = p + 1; return 0; } static inline int kiwi_readn(uint32_t n, char **pos, uint32_t *size) { char *end = *pos + *size; char *next = *pos + n; if (kiwi_unlikely(next > end)) { return -1; } *size -= (uint32_t)(next - *pos); *pos = next; return 0; } static inline void kiwi_write8to(char *dest, uint8_t value) { *dest = (char)value; } static inline void kiwi_write16to(char *dest, uint16_t value) { dest[0] = (value >> 8) & 255; dest[1] = value & 255; } static inline void kiwi_write32to(char *dest, uint32_t value) { dest[0] = (value >> 24) & 255; dest[1] = (value >> 16) & 255; dest[2] = (value >> 8) & 255; dest[3] = value & 255; } KIWI_API static inline void kiwi_header_set_size(kiwi_header_t *header, uint32_t size) { kiwi_write32to((char *)&header->len, size - 1); } static inline void kiwi_write8(char **pos, uint8_t value) { kiwi_write8to(*pos, value); *pos = *pos + sizeof(value); } static inline void kiwi_write16(char **pos, uint16_t value) { kiwi_write16to(*pos, value); *pos = *pos + sizeof(value); } static inline void kiwi_write32(char **pos, uint32_t value) { kiwi_write32to(*pos, value); *pos = *pos + sizeof(value); } static inline void kiwi_write(char **pos, const char *buf, int size) { memcpy(*pos, buf, size); *pos = *pos + size; } KIWI_API static inline int kiwi_read(uint32_t *len, char **data, uint32_t *size) { if (*size < sizeof(kiwi_header_t)) { return sizeof(kiwi_header_t) - *size; } uint32_t pos_size = *size - sizeof(uint8_t); char *pos = *data + sizeof(uint8_t); /* type */ kiwi_read32(len, &pos, &pos_size); uint32_t len_to_read; len_to_read = (*len + sizeof(char)) - *size; if (len_to_read > 0) { return len_to_read; } *data += sizeof(uint8_t) + *len; *size -= sizeof(uint8_t) + *len; *len -= sizeof(uint32_t); return 0; } KIWI_API static inline uint32_t kiwi_read_size(char *data, uint32_t data_size) { assert(data_size >= sizeof(kiwi_header_t)); /* type */ uint32_t pos_size = data_size - sizeof(uint8_t); char *pos = data + sizeof(uint8_t); /* size */ uint32_t size = 0; kiwi_read32(&size, &pos, &pos_size); return size; } KIWI_API static inline uint32_t kiwi_read_startup_size(char *data, uint32_t data_size) { assert(data_size >= sizeof(uint32_t)); /* size */ uint32_t size = 0; kiwi_read32(&size, &data, &data_size); size -= sizeof(uint32_t); return size; } /* constants from PG sources */ #define PQ_SMALL_MESSAGE_LIMIT 10000 #define PQ_LARGE_MESSAGE_LIMIT ((size_t)0x3fffffff - 2) KIWI_API static inline int kiwi_validate_startup_header(char *data, uint32_t data_size, uint32_t *size) { (void)data_size; /* Silent Compiler warnings */ assert(data_size >= sizeof(uint32_t)); *size = kiwi_read_startup_size(data, sizeof(uint32_t)); /* do not expect big startup messages */ if (kiwi_unlikely(*size > PQ_SMALL_MESSAGE_LIMIT)) { return -1; } return 0; } KIWI_API static inline int kiwi_validate_fe_header(char *data, uint32_t data_size, uint32_t *size) { (void)data_size; /* Silent Compiler warnings */ assert(data_size >= sizeof(kiwi_header_t)); *size = kiwi_read_size(data, sizeof(kiwi_header_t)); kiwi_header_t *header = (kiwi_header_t *)data; if (kiwi_unlikely(*size < sizeof(uint32_t))) { return -1; } /* check supported protocol message type */ if (kiwi_unlikely(header->type < 0x20)) { return -1; } uint32_t limit = PQ_LARGE_MESSAGE_LIMIT; switch (header->type) { case KIWI_FE_COPY_DONE: case KIWI_FE_COPY_FAIL: case KIWI_FE_FLUSH: case KIWI_FE_SYNC: case KIWI_FE_TERMINATE: case KIWI_FE_CLOSE: case KIWI_FE_DESCRIBE: case KIWI_FE_EXECUTE: limit = PQ_SMALL_MESSAGE_LIMIT; break; } if (kiwi_likely(*size > limit)) { return -1; } return 0; } KIWI_API static inline int kiwi_validate_be_header(char *data, uint32_t data_size, uint32_t *size) { (void)data_size; /* Silent Compiler warnings */ assert(data_size >= sizeof(kiwi_header_t)); *size = kiwi_read_size(data, sizeof(kiwi_header_t)); kiwi_header_t *header = (kiwi_header_t *)data; if (kiwi_unlikely(*size < sizeof(uint32_t))) { return -1; } /* check supported protocol message type */ if (kiwi_unlikely(header->type < 0x20)) { return -1; } if (kiwi_likely(*size > PQ_LARGE_MESSAGE_LIMIT)) { return -1; } return 0; } odyssey-1.6.0-rc1/sources/include/kiwi/key.h000066400000000000000000000005731526064202200207160ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_key kiwi_key_t; struct kiwi_key { uint32_t key; uint32_t key_pid; }; static inline void kiwi_key_init(kiwi_key_t *key) { key->key = 0; key->key_pid = 0; } static inline int kiwi_key_cmp(kiwi_key_t *a, kiwi_key_t *b) { return a->key == b->key && a->key_pid == b->key_pid; } odyssey-1.6.0-rc1/sources/include/kiwi/kiwi.h000066400000000000000000000013371526064202200210700ustar00rootroot00000000000000#ifndef KIWI_H #define KIWI_H /* * kiwi. * * postgreSQL protocol interaction library. */ #include #include #include #include #include #include #include #include #include #include #include "kiwi/sasl.h" #include "kiwi/macro.h" #include "kiwi/header.h" #include "kiwi/error_codes.h" #include "kiwi/io.h" #include "kiwi/key.h" #include "kiwi/md5.h" #include "kiwi/password.h" #include "kiwi/var.h" #include "kiwi/param.h" #include "kiwi/param_lock.h" #include "kiwi/options.h" #include "kiwi/fe_read.h" #include "kiwi/be_read.h" #include "kiwi/fe_write.h" #include "kiwi/be_write.h" #endif /* KIWI_H */ odyssey-1.6.0-rc1/sources/include/kiwi/macro.h000066400000000000000000000004761526064202200212310ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ #define kiwi_likely(expr) __builtin_expect(!!(expr), 1) #define kiwi_unlikely(expr) __builtin_expect(!!(expr), 0) #define kiwi_container_of(ptr, type, field) \ ((type *)((char *)(ptr) - __builtin_offsetof(type, field))) #define KIWI_API odyssey-1.6.0-rc1/sources/include/kiwi/md5.h000066400000000000000000000006211526064202200206050ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_md5 kiwi_md5_t; struct kiwi_md5 { uint32_t state[4]; uint64_t count; uint8_t buffer[64]; }; void kiwi_md5_init(kiwi_md5_t *); void kiwi_md5_update(kiwi_md5_t *, void *input, size_t len); void kiwi_md5_final(kiwi_md5_t *, uint8_t digest[16]); void kiwi_md5_tostring(char *dest, uint8_t digest[16]); odyssey-1.6.0-rc1/sources/include/kiwi/options.h000066400000000000000000000002761526064202200216210ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ int kiwi_parse_options_and_update_vars(kiwi_vars_t *vars, const char *pgoptions, int pgoptions_len); odyssey-1.6.0-rc1/sources/include/kiwi/param.h000066400000000000000000000056531526064202200212320ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_param kiwi_param_t; typedef struct kiwi_params kiwi_params_t; struct kiwi_param { int name_len; int value_len; kiwi_param_t *next; char data[]; }; struct kiwi_params { int count; kiwi_param_t *list; }; static inline kiwi_param_t *kiwi_param_allocate(char *name, int name_len, char *value, int value_len) { kiwi_param_t *param; param = malloc(sizeof(kiwi_param_t) + name_len + value_len); if (param == NULL) { return NULL; } param->name_len = name_len; param->value_len = value_len; param->next = NULL; memcpy(param->data, name, name_len); memcpy(param->data + name_len, value, value_len); return param; } static inline void kiwi_param_free(kiwi_param_t *param) { free(param); } static inline char *kiwi_param_name(kiwi_param_t *param) { return param->data; } static inline char *kiwi_param_value(kiwi_param_t *param) { return param->data + param->name_len; } static inline int kiwi_param_compare_name(kiwi_param_t *param, char *name, int name_len) { int rc; rc = param->name_len == name_len && strncasecmp(kiwi_param_name(param), name, name_len) == 0; return rc; } static inline int kiwi_param_compare(kiwi_param_t *a, kiwi_param_t *b) { return kiwi_param_compare_name(a, kiwi_param_name(b), b->name_len); } static inline void kiwi_params_init(kiwi_params_t *params) { params->list = NULL; params->count = 0; } static inline void kiwi_params_free(kiwi_params_t *params) { kiwi_param_t *param = params->list; while (param) { kiwi_param_t *next = param->next; kiwi_param_free(param); param = next; } } static inline void kiwi_params_add(kiwi_params_t *params, kiwi_param_t *param) { param->next = params->list; params->list = param; params->count++; } static inline int kiwi_params_copy(kiwi_params_t *dest, kiwi_params_t *src) { kiwi_param_t *param = src->list; while (param) { kiwi_param_t *new_param; new_param = kiwi_param_allocate(kiwi_param_name(param), param->name_len, kiwi_param_value(param), param->value_len); if (new_param == NULL) { return -1; } kiwi_params_add(dest, new_param); param = param->next; } return 0; } static inline void kiwi_params_replace(kiwi_params_t *params, kiwi_param_t *new_param) { kiwi_param_t *param = params->list; kiwi_param_t *prev = NULL; while (param) { if (kiwi_param_compare(param, new_param)) { new_param->next = param->next; if (prev) { prev->next = new_param; } else { params->list = new_param; } kiwi_param_free(param); return; } prev = param; param = param->next; } kiwi_params_add(params, new_param); } static inline kiwi_param_t *kiwi_params_find(kiwi_params_t *params, char *name, int name_len) { kiwi_param_t *param = params->list; while (param) { if (kiwi_param_compare_name(param, name, name_len)) { return param; } param = param->next; } return NULL; } odyssey-1.6.0-rc1/sources/include/kiwi/param_lock.h000066400000000000000000000021731526064202200222340ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ #include typedef struct kiwi_params_lock kiwi_params_lock_t; struct kiwi_params_lock { mm_mutex_t lock; kiwi_params_t params; }; static inline void kiwi_params_lock_init(kiwi_params_lock_t *pl) { mm_mutex_init(&pl->lock); kiwi_params_init(&pl->params); } static inline void kiwi_params_lock_free(kiwi_params_lock_t *pl) { mm_mutex_destroy(&pl->lock); kiwi_params_free(&pl->params); } static inline int kiwi_params_lock_count(kiwi_params_lock_t *pl) { mm_mutex_lock2(&pl->lock); int rc = pl->params.count; mm_mutex_unlock(&pl->lock); return rc; } static inline int kiwi_params_lock_copy(kiwi_params_lock_t *pl, kiwi_params_t *dest) { mm_mutex_lock2(&pl->lock); int rc; rc = kiwi_params_copy(dest, &pl->params); mm_mutex_unlock(&pl->lock); return rc; } static inline int kiwi_params_lock_set_once(kiwi_params_lock_t *pl, kiwi_params_t *params) { mm_mutex_lock2(&pl->lock); if (pl->params.count > 0) { mm_mutex_unlock(&pl->lock); return 0; } pl->params = *params; mm_mutex_unlock(&pl->lock); return 1; } odyssey-1.6.0-rc1/sources/include/kiwi/password.h000066400000000000000000000044101526064202200217620ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_password kiwi_password_t; struct kiwi_password { char *password; int password_len; }; static inline void kiwi_password_init(kiwi_password_t *pw) { pw->password = NULL; pw->password_len = 0; } static inline void kiwi_password_copy(kiwi_password_t *dst_pw, const kiwi_password_t *src_pw) { assert(dst_pw->password_len == 0); assert(dst_pw->password == NULL); dst_pw->password_len = src_pw->password_len; dst_pw->password = (char *)malloc(sizeof(char) * src_pw->password_len); strncpy(dst_pw->password, src_pw->password, src_pw->password_len); } static inline void kiwi_password_free(kiwi_password_t *pw) { if (pw->password) { free(pw->password); } } static inline int kiwi_password_compare(kiwi_password_t *a, kiwi_password_t *b) { return (a->password_len == b->password_len) && (memcmp(a->password, b->password, a->password_len) == 0); } static inline uint32_t kiwi_password_salt(kiwi_key_t *key, uint32_t rand) { return rand ^ key->key ^ key->key_pid; } __attribute__((hot)) static inline int kiwi_password_md5(kiwi_password_t *pw, char *user, int user_len, char *password, int password_len, char salt[4]) { uint8_t digest_prepare[16]; char digest_prepare_sz[32]; uint8_t digest[16]; kiwi_md5_t ctx; if (password_len == 35 && memcmp(password, "md5", 3) == 0) { /* digest = md5(digest, salt) */ kiwi_md5_init(&ctx); kiwi_md5_update(&ctx, password + 3, 32); kiwi_md5_update(&ctx, salt, 4); kiwi_md5_final(&ctx, digest); } else { /* digest = md5(password, user) */ kiwi_md5_init(&ctx); kiwi_md5_update(&ctx, password, password_len); kiwi_md5_update(&ctx, user, user_len); kiwi_md5_final(&ctx, digest_prepare); kiwi_md5_tostring(digest_prepare_sz, digest_prepare); /* digest = md5(digest, salt) */ kiwi_md5_init(&ctx); kiwi_md5_update(&ctx, digest_prepare_sz, 32); kiwi_md5_update(&ctx, salt, 4); kiwi_md5_final(&ctx, digest); } /* 'md5' + to_string(digest) */ pw->password_len = 35 + 1; pw->password = malloc(pw->password_len); if (pw->password == NULL) { return -1; } pw->password[0] = 'm'; pw->password[1] = 'd'; pw->password[2] = '5'; kiwi_md5_tostring((pw->password + 3), digest); pw->password[35] = 0; return 0; } odyssey-1.6.0-rc1/sources/include/kiwi/sasl.h000066400000000000000000000002101526064202200210540ustar00rootroot00000000000000#pragma once typedef enum { KIWI_BE_SASL_INIT = 10, KIWI_BE_SASL_CONTINUE = 11, KIWI_BE_SASL_FINAL = 12, } kiwi_be_sasl_msg_type_t; odyssey-1.6.0-rc1/sources/include/kiwi/var.h000066400000000000000000000217131526064202200207150ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ #define KIWI_MAX_VAR_SIZE 128 typedef struct kiwi_var kiwi_var_t; typedef struct kiwi_vars kiwi_vars_t; typedef struct untyped_kiwi_var untyped_kiwi_var_t; typedef enum { KIWI_VAR_CLIENT_ENCODING, KIWI_VAR_DATESTYLE, KIWI_VAR_TIMEZONE, KIWI_VAR_STANDARD_CONFORMING_STRINGS, KIWI_VAR_APPLICATION_NAME, KIWI_VAR_COMPRESSION, KIWI_VAR_SEARCH_PATH, KIWI_VAR_STATEMENT_TIMEOUT, KIWI_VAR_WORK_MEM, KIWI_VAR_LOCK_TIMEOUT, KIWI_VAR_LOG_STATEMENT, KIWI_VAR_LOG_MIN_MESSAGES, KIWI_VAR_LOG_MIN_ERROR_STATEMENT, KIWI_VAR_LOG_MIN_DURATION_STATEMENT, KIWI_VAR_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, KIWI_VAR_DEFAULT_TABLE_ACCESS_METHOD, KIWI_VAR_DEFAULT_TOAST_COMPRESSION, KIWI_VAR_CHECK_FUNCTION_BODIES, KIWI_VAR_DEFAULT_TRANSACTION_ISOLATION, KIWI_VAR_DEFAULT_TRANSACTION_READ_ONLY, KIWI_VAR_DEFAULT_TRANSACTION_DEFERRABLE, KIWI_VAR_TRANSACTION_ISOLATION, KIWI_VAR_TRANSACTION_READ_ONLY, KIWI_VAR_IDLE_SESSION_TIMEOUT, KIWI_VAR_IS_HOT_STANDBY, KIWI_VAR_ROLE, KIWI_VAR_SPQRGUARD_PREVENT_DISTRIBUTED_TABLE_MODIFY, KIWI_VAR_SPQRGUARD_PREVENT_REFERENCE_TABLE_MODIFY, KIWI_VAR_PQ_TEST_PROTOCOL_NEGOTIATION, /* greenplum */ KIWI_VAR_GP_SESSION_ROLE, /* odyssey own params */ KIWI_VAR_ODYSSEY_CATCHUP_TIMEOUT, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, /* always keep this at end */ KIWI_VAR_MAX, KIWI_VAR_UNDEF } kiwi_var_type_t; struct kiwi_var { kiwi_var_type_t type; char *name; int name_len; char value[KIWI_MAX_VAR_SIZE]; int value_len; }; struct untyped_kiwi_var { char *name; size_t name_len; char *value; size_t value_len; }; struct kiwi_vars { kiwi_var_t vars[KIWI_VAR_MAX]; }; static inline void kiwi_var_init(kiwi_var_t *var, char *name, int name_len) { var->type = KIWI_VAR_UNDEF; var->name = name; var->name_len = name_len; var->value_len = 0; } static inline int kiwi_var_set(kiwi_var_t *var, kiwi_var_type_t type, const char *value, int value_len) { var->type = type; if (value_len > (int)sizeof(var->value)) { return -1; } memcpy(var->value, value, value_len); var->value_len = value_len; return 0; } static inline void kiwi_var_unset(kiwi_var_t *var) { var->type = KIWI_VAR_UNDEF; var->value_len = 0; } static inline int kiwi_var_compare(kiwi_var_t *a, kiwi_var_t *b) { if (a->type != b->type) { return 0; } if (a->value_len != b->value_len) { return 0; } return memcmp(a->value, b->value, a->value_len) == 0; } static inline kiwi_var_t *kiwi_vars_of(kiwi_vars_t *vars, kiwi_var_type_t type) { return &vars->vars[type]; } static inline kiwi_var_t *kiwi_vars_get(kiwi_vars_t *vars, kiwi_var_type_t type) { if (type == KIWI_VAR_UNDEF) { return NULL; } if (vars->vars[type].type != KIWI_VAR_UNDEF) { return &vars->vars[type]; } return NULL; } static inline void kiwi_vars_init(kiwi_vars_t *vars) { kiwi_var_init(&vars->vars[KIWI_VAR_CLIENT_ENCODING], "client_encoding", 16); kiwi_var_init(&vars->vars[KIWI_VAR_DATESTYLE], "DateStyle", 10); kiwi_var_init(&vars->vars[KIWI_VAR_TIMEZONE], "TimeZone", 9); kiwi_var_init(&vars->vars[KIWI_VAR_STANDARD_CONFORMING_STRINGS], "standard_conforming_strings", 28); kiwi_var_init(&vars->vars[KIWI_VAR_APPLICATION_NAME], "application_name", 17); kiwi_var_init(&vars->vars[KIWI_VAR_COMPRESSION], "compression", 12); kiwi_var_init(&vars->vars[KIWI_VAR_SEARCH_PATH], "search_path", 12); kiwi_var_init(&vars->vars[KIWI_VAR_STATEMENT_TIMEOUT], "statement_timeout", sizeof("statement_timeout")); kiwi_var_init(&vars->vars[KIWI_VAR_WORK_MEM], "work_mem", sizeof("work_mem")); kiwi_var_init(&vars->vars[KIWI_VAR_LOCK_TIMEOUT], "lock_timeout", sizeof("lock_timeout")); kiwi_var_init(&vars->vars[KIWI_VAR_LOG_STATEMENT], "log_statement", sizeof("log_statement")); kiwi_var_init(&vars->vars[KIWI_VAR_LOG_MIN_MESSAGES], "log_min_messages", sizeof("log_min_messages")); kiwi_var_init(&vars->vars[KIWI_VAR_LOG_MIN_ERROR_STATEMENT], "log_min_error_statement", sizeof("log_min_error_statement")); kiwi_var_init(&vars->vars[KIWI_VAR_LOG_MIN_DURATION_STATEMENT], "log_min_duration_statement", sizeof("log_min_duration_statement")); kiwi_var_init(&vars->vars[KIWI_VAR_IDLE_IN_TRANSACTION_SESSION_TIMEOUT], "idle_in_transaction_session_timeout", sizeof("idle_in_transaction_session_timeout")); kiwi_var_init(&vars->vars[KIWI_VAR_DEFAULT_TABLE_ACCESS_METHOD], "default_table_access_method", sizeof("default_table_access_method")); kiwi_var_init(&vars->vars[KIWI_VAR_DEFAULT_TOAST_COMPRESSION], "default_toast_compression", sizeof("default_toast_compression")); kiwi_var_init(&vars->vars[KIWI_VAR_CHECK_FUNCTION_BODIES], "check_function_bodies", sizeof("check_function_bodies")); kiwi_var_init(&vars->vars[KIWI_VAR_DEFAULT_TRANSACTION_ISOLATION], "default_transaction_isolation", sizeof("default_transaction_isolation")); kiwi_var_init(&vars->vars[KIWI_VAR_DEFAULT_TRANSACTION_READ_ONLY], "default_transaction_read_only", sizeof("default_transaction_read_only")); kiwi_var_init(&vars->vars[KIWI_VAR_DEFAULT_TRANSACTION_DEFERRABLE], "default_transaction_deferrable", sizeof("default_transaction_deferrable")); kiwi_var_init(&vars->vars[KIWI_VAR_TRANSACTION_ISOLATION], "transaction_isolation", sizeof("transaction_isolation")); kiwi_var_init(&vars->vars[KIWI_VAR_TRANSACTION_READ_ONLY], "transaction_read_only", sizeof("transaction_read_only")); kiwi_var_init(&vars->vars[KIWI_VAR_IDLE_SESSION_TIMEOUT], "idle_session_timeout", sizeof("idle_session_timeout")); kiwi_var_init(&vars->vars[KIWI_VAR_GP_SESSION_ROLE], "gp_session_role", sizeof("gp_session_role")); kiwi_var_init(&vars->vars[KIWI_VAR_IS_HOT_STANDBY], "is_hot_standby", sizeof("is_hot_standby")); kiwi_var_init(&vars->vars[KIWI_VAR_ODYSSEY_CATCHUP_TIMEOUT], "odyssey_catchup_timeout", sizeof("odyssey_catchup_timeout")); kiwi_var_init(&vars->vars[KIWI_VAR_PQ_TEST_PROTOCOL_NEGOTIATION], "_pq_.test_protocol_negotiation", sizeof("_pq_.test_protocol_negotiation")); /* XXX: todo - also accept aliases */ kiwi_var_init(&vars->vars[KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS], "odyssey.target_session_attrs", sizeof("odyssey.target_session_attrs")); kiwi_var_init(&vars->vars[KIWI_VAR_ROLE], "role", sizeof("role")); kiwi_var_init( &vars->vars[KIWI_VAR_SPQRGUARD_PREVENT_DISTRIBUTED_TABLE_MODIFY], "spqrguard.prevent_distributed_table_modify", sizeof("spqrguard.prevent_distributed_table_modify")); kiwi_var_init( &vars->vars[KIWI_VAR_SPQRGUARD_PREVENT_REFERENCE_TABLE_MODIFY], "spqrguard.prevent_reference_table_modify", sizeof("spqrguard.prevent_reference_table_modify")); } static inline int kiwi_vars_set(kiwi_vars_t *vars, kiwi_var_type_t type, const char *value, int value_len) { return kiwi_var_set(kiwi_vars_of(vars, type), type, value, value_len); } static inline void kiwi_vars_unset(kiwi_vars_t *vars, kiwi_var_type_t type) { kiwi_var_unset(kiwi_vars_of(vars, type)); } static inline kiwi_var_type_t kiwi_vars_find(kiwi_vars_t *vars, char *name, int name_len) { kiwi_var_type_t type; type = KIWI_VAR_CLIENT_ENCODING; for (; type < KIWI_VAR_MAX; type++) { kiwi_var_t *var = kiwi_vars_of(vars, type); if (var->name_len != name_len) { continue; } if (!strncasecmp(var->name, name, name_len)) { return type; } } return KIWI_VAR_UNDEF; } static inline int kiwi_vars_override(kiwi_vars_t *vars, kiwi_vars_t *override_vars) { kiwi_var_type_t type = 0; for (; type < KIWI_VAR_MAX; type++) { kiwi_var_t *var = kiwi_vars_of(override_vars, type); if (!var->value_len) { continue; } kiwi_vars_set(vars, type, var->value, var->value_len); } return 0; } static inline int kiwi_vars_update(kiwi_vars_t *vars, char *name, int name_len, char *value, int value_len) { kiwi_var_type_t type; type = kiwi_vars_find(vars, name, name_len); if (type == KIWI_VAR_UNDEF) { return -1; } if (type == KIWI_VAR_IS_HOT_STANDBY) { /* skip volatile params caching */ return 0; } kiwi_vars_set(vars, type, value, value_len); return 0; } static inline int kiwi_vars_update_both(kiwi_vars_t *a, kiwi_vars_t *b, char *name, int name_len, char *value, int value_len) { kiwi_var_type_t type; type = kiwi_vars_find(a, name, name_len); if (type == KIWI_VAR_UNDEF) { return -1; } kiwi_vars_set(a, type, value, value_len); kiwi_vars_set(b, type, value, value_len); return 0; } static inline int kiwi_enquote(char *src, char *dst, int dst_len) { if (dst_len < 4) { return -1; } char *pos = dst; char *end = dst + dst_len - 4; *pos++ = 'E'; *pos++ = '\''; while (*src && pos < end) { if (*src == '\'') { *pos++ = '\''; } else if (*src == '\\') { *pos++ = '\\'; } *pos++ = *src++; } if (*src || pos > end) { return -1; } *pos++ = '\''; *pos = 0; return (int)(pos - dst); } int kiwi_vars_cas(kiwi_vars_t *client, kiwi_vars_t *server, char *query, int query_len, int smart_enquoting); odyssey-1.6.0-rc1/sources/include/ldap_endpoint.h000066400000000000000000000036011526064202200217760ustar00rootroot00000000000000#pragma once #include #include #include #include typedef struct { mm_mutex_t lock; char *name; char *ldapserver; uint64_t ldapport; /* either null, ldap or ldaps */ char *ldapscheme; char *ldapprefix; char *ldapsuffix; char *ldapbindpasswd; char *ldapsearchfilter; char *ldapsearchattribute; char *ldapscope; char *ldapbasedn; char *ldapbinddn; /* preparsed connect url */ char *ldapurl; od_list_t link; atomic_int_fast64_t refs; } od_ldap_endpoint_t; /* ldap endpoints ADD/REMOVE API */ extern od_retcode_t od_ldap_endpoint_prepare(od_ldap_endpoint_t *); extern od_retcode_t od_ldap_endpoint_add(od_ldap_endpoint_t *ldaps, od_ldap_endpoint_t *target); extern od_ldap_endpoint_t *od_ldap_endpoint_find(od_list_t *ldaps, char *target); extern od_retcode_t od_ldap_endpoint_remove(od_ldap_endpoint_t *ldaps, od_ldap_endpoint_t *target); /* ------------------------------------------------------- */ extern od_ldap_endpoint_t *od_ldap_endpoint_alloc(void); extern od_retcode_t od_ldap_endpoint_init(od_ldap_endpoint_t *); extern od_retcode_t od_ldap_endpoint_free(od_ldap_endpoint_t *le); extern od_ldap_endpoint_t *od_ldap_endpoint_ref(od_ldap_endpoint_t *le); /* ldap_storage_credentials */ typedef struct { char *name; char *lsc_username; char *lsc_password; od_list_t link; atomic_int_fast64_t refs; } od_ldap_storage_credentials_t; extern od_ldap_storage_credentials_t * od_ldap_storage_credentials_find(od_list_t *storage_users, char *target); /* ------------------------------------------------------- */ extern od_ldap_storage_credentials_t *od_ldap_storage_credentials_alloc(void); extern od_ldap_storage_credentials_t * od_ldap_storage_credentials_ref(od_ldap_storage_credentials_t *); extern od_retcode_t od_ldap_storage_credentials_free(od_ldap_storage_credentials_t *lsc); odyssey-1.6.0-rc1/sources/include/list.h000066400000000000000000000030401526064202200201260ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef struct od_list od_list_t; struct od_list { od_list_t *next; od_list_t *prev; }; static inline void od_list_init(od_list_t *list) { list->next = list->prev = list; } static inline void od_list_append(od_list_t *list, od_list_t *node) { node->next = list; node->prev = list->prev; node->prev->next = node; node->next->prev = node; } static inline void od_list_unlink(od_list_t *node) { node->prev->next = node->next; node->next->prev = node->prev; } static inline void od_list_push(od_list_t *list, od_list_t *node) { node->next = list->next; node->prev = list; node->prev->next = node; node->next->prev = node; } static inline od_list_t *od_list_pop(od_list_t *list) { register od_list_t *pop = list->next; od_list_unlink(pop); return pop; } static inline od_list_t *od_list_pop_back(od_list_t *list) { register od_list_t *pop = list->prev; od_list_unlink(pop); return pop; } static inline int od_list_empty(od_list_t *list) { return list->next == list && list->prev == list; } #define od_list_foreach(list, iterator) \ for (iterator = (list)->next; iterator != list; \ iterator = (iterator)->next) #define od_list_foreach_safe(list, iterator, safe) \ for (iterator = (list)->next; \ iterator != list && (safe = iterator->next); iterator = safe) static inline size_t od_list_count(od_list_t *list) { size_t count = 0; od_list_t *i, *n; od_list_foreach_safe (list, i, n) { ++count; } return count; } odyssey-1.6.0-rc1/sources/include/logger.h000066400000000000000000000236021526064202200204400ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #define OD_LOGLINE_MAXLEN 1024 #define OD_LOGGER_FORMAT_MAX_TOKENS 128 #define OD_LOGGER_GLOBAL NULL typedef struct od_logger od_logger_t; typedef enum { OD_LOG, OD_ERROR, OD_DEBUG, OD_FATAL } od_logger_level_t; typedef enum { OD_LOGGER_ONLINE, OD_LOGGER_REOPENING, OD_LOGGER_OFFLINE } od_logger_state_t; typedef enum { OD_LOGGER_FORMAT_TEXT, OD_LOGGER_FORMAT_JSON } od_logger_format_type_t; typedef enum { OD_FMT_LITERAL, /* literal text run */ OD_FMT_PID, /* %p */ OD_FMT_TID, /* %T */ OD_FMT_TIMESTAMP, /* %t */ OD_FMT_MILLIS, /* %e */ OD_FMT_UNIXTIME, /* %n */ OD_FMT_LEVEL, /* %l */ OD_FMT_CONTEXT, /* %c */ OD_FMT_MESSAGE, /* %m */ OD_FMT_MESSAGE_ESC, /* %M */ OD_FMT_CLIENT_ID, /* %i */ OD_FMT_SERVER_ID, /* %s */ OD_FMT_USER, /* %u */ OD_FMT_DATABASE, /* %d */ OD_FMT_APPLICATION_NAME, /* %a */ OD_FMT_EXTERNAL_ID, /* %x */ OD_FMT_CLIENT_HOST, /* %h */ OD_FMT_CLIENT_PORT, /* %r */ OD_FMT_SERVER_HOST /* %H */ } od_fmt_token_type_t; typedef struct { od_fmt_token_type_t type; /* owner is original format string */ const char *literal; int literal_len; } od_fmt_token_t; typedef struct { od_logger_level_t level; mm_lf_stack_entry_t link; mm_mpsc_node_t node; size_t len; char text[OD_LOGLINE_MAXLEN]; } od_logger_slot_t; struct od_logger { od_pid_t *pid; int log_debug; int log_stdout; int log_syslog; int async; int queue_depth; char *format; int format_len; od_logger_format_type_t format_type; /* precompiled format-tokens */ od_fmt_token_t tokens[OD_LOGGER_FORMAT_MAX_TOKENS]; int tokens_count; atomic_int fd; atomic_int batching; atomic_uint_fast64_t state; int64_t machine; od_logger_slot_t *slots; mm_mpsc_queue_t tasks; /* * The last node popped from tasks. It is now the queue's head * sentinel, so its `next` may still be written by a producer. * We cannot return it to free_slots until the next pop() advances * head past it. Recycled in the next process_log_queue() call. */ od_logger_slot_t *pending_slot; mm_lf_stack_t free_slots; mm_wait_list_t notifier; atomic_uint_fast64_t dropped_lines; }; extern od_retcode_t od_logger_init(od_logger_t *, od_pid_t *); extern od_retcode_t od_logger_load(od_logger_t *logger); void od_logger_stat(od_logger_t *logger); static inline void od_logger_set_debug(od_logger_t *logger, int enable) { logger->log_debug = enable; } static inline void od_logger_set_stdout(od_logger_t *logger, int enable) { logger->log_stdout = enable; } void od_logger_set_format(od_logger_t *logger, char *format); static inline void od_logger_set_async(od_logger_t *logger, int async) { logger->async = async; } static inline void od_logger_set_queue_depth(od_logger_t *logger, int queue_depth) { if (queue_depth <= 0) { queue_depth = 30000; } logger->queue_depth = queue_depth; } extern int od_logger_open(od_logger_t *, const char *); extern int od_logger_reopen(od_logger_t *); extern int od_logger_open_syslog(od_logger_t *, char *, char *); extern void od_logger_shutdown(od_logger_t *); extern void od_logger_close(od_logger_t *); extern void od_logger_write(od_logger_t *, od_logger_level_t, char *, void *, void *, char *, va_list) __attribute__((format(printf, 6, 0))); void od_logger_wait_finish(od_logger_t *); void od_logger_flush(od_logger_t *); static inline void od_log(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 5, 6))); static inline void od_glog(char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 4, 5))); static inline void od_debug(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 5, 6))); static inline void od_gdebug(char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 4, 5))); static inline void od_error(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 5, 6))); static inline void od_gerror(char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 4, 5))); static inline void od_fatal(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 5, 6))); static inline void od_gfatal(char *context, void *client, void *server, char *fmt, ...) __attribute__((format(printf, 4, 5))); static inline void od_vglog(char *context, void *client, void *server, char *fmt, va_list args) __attribute__((format(printf, 4, 0))); static inline void od_vgdebug(char *context, void *client, void *server, char *fmt, va_list args) __attribute__((format(printf, 4, 0))); static inline void od_vgerror(char *context, void *client, void *server, char *fmt, va_list args) __attribute__((format(printf, 4, 0))); static inline void od_vgfatal(char *context, void *client, void *server, char *fmt, va_list args) __attribute__((format(printf, 4, 0))); static inline void od_log(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(logger, OD_LOG, context, client, server, fmt, args); va_end(args); } static inline void od_glog(char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(OD_LOGGER_GLOBAL, OD_LOG, context, client, server, fmt, args); va_end(args); } static inline void od_debug(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(logger, OD_DEBUG, context, client, server, fmt, args); va_end(args); } static inline void od_gdebug(char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(OD_LOGGER_GLOBAL, OD_DEBUG, context, client, server, fmt, args); va_end(args); } static inline void od_error(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(logger, OD_ERROR, context, client, server, fmt, args); va_end(args); } static inline void od_gerror(char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(OD_LOGGER_GLOBAL, OD_ERROR, context, client, server, fmt, args); va_end(args); } static inline void od_fatal(od_logger_t *logger, char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(logger, OD_FATAL, context, client, server, fmt, args); va_end(args); exit(1); } static inline void od_gfatal(char *context, void *client, void *server, char *fmt, ...) { va_list args; va_start(args, fmt); od_logger_write(OD_LOGGER_GLOBAL, OD_FATAL, context, client, server, fmt, args); va_end(args); exit(1); } /* log functions to create module-owned loggers with predefined values */ static inline void od_vglog(char *context, void *client, void *server, char *fmt, va_list args) { od_logger_write(OD_LOGGER_GLOBAL, OD_LOG, context, client, server, fmt, args); } static inline void od_vgdebug(char *context, void *client, void *server, char *fmt, va_list args) { od_logger_write(OD_LOGGER_GLOBAL, OD_DEBUG, context, client, server, fmt, args); } static inline void od_vgerror(char *context, void *client, void *server, char *fmt, va_list args) { od_logger_write(OD_LOGGER_GLOBAL, OD_ERROR, context, client, server, fmt, args); } static inline void od_vgfatal(char *context, void *client, void *server, char *fmt, va_list args) { od_logger_write(OD_LOGGER_GLOBAL, OD_FATAL, context, client, server, fmt, args); exit(1); } #define DEFINE_SIMPLE_MODULE_LOGGER(mod, ctx) \ __attribute__((unused)) static inline void mod##_log(char *fmt, ...) \ { \ va_list args; \ va_start(args, fmt); \ od_vglog(ctx, NULL, NULL, fmt, args); \ va_end(args); \ } \ __attribute__((unused)) static inline void mod##_debug(char *fmt, ...) \ { \ va_list args; \ va_start(args, fmt); \ od_vgdebug(ctx, NULL, NULL, fmt, args); \ va_end(args); \ } \ __attribute__((unused)) static inline void mod##_error(char *fmt, ...) \ { \ va_list args; \ va_start(args, fmt); \ od_vgerror(ctx, NULL, NULL, fmt, args); \ va_end(args); \ } \ __attribute__((unused)) static inline void mod##_fatal(char *fmt, ...) \ { \ va_list args; \ va_start(args, fmt); \ od_vgfatal(ctx, NULL, NULL, fmt, args); \ va_end(args); \ } odyssey-1.6.0-rc1/sources/include/machinarium/000077500000000000000000000000001526064202200213025ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/machinarium/bind.h000066400000000000000000000002351526064202200223670ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #define MM_BINDWITH_SO_REUSEPORT 1 << 1 #define MM_BINDWITH_SO_REUSEADDR 1 << 2 odyssey-1.6.0-rc1/sources/include/machinarium/buf.h000066400000000000000000000034521526064202200222330ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_buf mm_buf_t; struct mm_buf { char *start; char *pos; char *end; }; static inline void mm_buf_init(mm_buf_t *buf) { buf->start = NULL; buf->pos = NULL; buf->end = NULL; } static inline void mm_buf_free(mm_buf_t *buf) { if (buf->start == NULL) { return; } mm_free(buf->start); buf->start = NULL; buf->pos = NULL; buf->end = NULL; } static inline int mm_buf_size(mm_buf_t *buf) { return buf->end - buf->start; } static inline int mm_buf_used(mm_buf_t *buf) { return buf->pos - buf->start; } static inline int mm_buf_unused(mm_buf_t *buf) { return buf->end - buf->pos; } static inline void mm_buf_reset(mm_buf_t *buf) { buf->pos = buf->start; } static inline int mm_buf_shrink(mm_buf_t *buf, int size) { if (mm_buf_size(buf) <= size) { mm_buf_reset(buf); return 0; } char *p = mm_realloc(buf->start, size); if (p == NULL) { return -1; } buf->start = p; buf->pos = p; buf->end = p + size; return 0; } static inline int mm_buf_ensure(mm_buf_t *buf, int size) { if (buf->end - buf->pos >= size) { return 0; } int sz = mm_buf_size(buf) * 2; int actual = mm_buf_used(buf) + size; if (actual > sz) { sz = actual; } char *p; p = mm_realloc(buf->start, sz); if (p == NULL) { return -1; } buf->pos = p + (buf->pos - buf->start); buf->end = p + sz; buf->start = p; assert((buf->end - buf->pos) >= size); return 0; } static inline void mm_buf_advance(mm_buf_t *buf, int size) { buf->pos += size; } static inline int mm_buf_add(mm_buf_t *buf, void *pointer, int size) { int rc = mm_buf_ensure(buf, size); if (rc == -1) { return -1; } memcpy(buf->pos, pointer, size); mm_buf_advance(buf, size); return 0; } odyssey-1.6.0-rc1/sources/include/machinarium/build.h.cmake000066400000000000000000000006361526064202200236360ustar00rootroot00000000000000#ifndef MM_BUILD_H_ #define MM_BUILD_H_ /* * machinarium. * * cooperative multitasking engine. */ /* AUTO-GENERATED (see build.h.cmake) */ #cmakedefine HAVE_VALGRIND 1 #cmakedefine USE_BORINGSSL 1 #cmakedefine HAVE_ASAN @HAVE_ASAN@ #cmakedefine HAVE_TSAN @HAVE_TSAN@ #cmakedefine USE_UCONTEXT #cmakedefine MM_MEM_PROF #cmakedefine USE_TCMALLOC #cmakedefine USE_TCMALLOC_PROFILE #endif /* MM_BUILD_H */ odyssey-1.6.0-rc1/sources/include/machinarium/call.h000066400000000000000000000021061526064202200223650ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_call mm_call_t; typedef void (*mm_cancel_t)(void *, void *arg); typedef enum { MM_CALL_NONE = 0, MM_CALL_SIGNAL, MM_CALL_EVENT, MM_CALL_SLEEP, MM_CALL_COND, MM_CALL_CHANNEL, MM_CALL_CONNECT, MM_CALL_ACCEPT, MM_CALL_HANDSHAKE } mm_calltype_t; struct mm_call { mm_calltype_t type; mm_coroutine_t *coroutine; mm_cancel_t cancel_function; void *arg; int timedout; int status; }; void mm_call(mm_call_t *, mm_calltype_t, uint32_t); static inline int mm_call_is(mm_call_t *call, mm_calltype_t type) { return call->type == type; } static inline int mm_call_is_active(mm_call_t *call) { return call->type != MM_CALL_NONE; } static inline int mm_call_is_aborted(mm_call_t *call) { return mm_call_is_active(call) && call->status != 0; } static inline void mm_call_cancel(mm_call_t *call, void *object) { if (!mm_call_is_active(call)) { return; } call->cancel_function(object, call->arg); } odyssey-1.6.0-rc1/sources/include/machinarium/channel.h000066400000000000000000000017061526064202200230670ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include typedef struct mm_channelrd mm_channelrd_t; typedef struct mm_channel mm_channel_t; struct mm_channelrd { mm_event_t event; mm_msg_t *result; mm_list_t link; }; struct mm_channel { mm_sleeplock_t lock; mm_list_t msg_list; int msg_list_count; mm_list_t readers; int readers_count; int chan_limit; mm_channel_limit_policy_t limit_policy; }; void mm_channel_init(mm_channel_t *); void mm_channel_free(mm_channel_t *); mm_retcode_t mm_channel_write(mm_channel_t *, mm_msg_t *); mm_msg_t *mm_channel_read(mm_channel_t *, uint32_t); mm_msg_t *mm_channel_read_back(mm_channel_t *, uint32_t); static inline int mm_channel_get_size(mm_channel_t *chan) { return chan->msg_list_count; } odyssey-1.6.0-rc1/sources/include/machinarium/channel_limit.h000066400000000000000000000002011526064202200242520ustar00rootroot00000000000000#pragma once typedef enum { MM_CHANNEL_UNLIMITED, MM_CHANNEL_LIMIT_HARD, MM_CHANNEL_LIMIT_SOFT, } mm_channel_limit_policy_t; odyssey-1.6.0-rc1/sources/include/machinarium/clock.h000066400000000000000000000022341526064202200225470ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include typedef struct mm_clock mm_clock_t; struct mm_clock { int active; int time_cached; uint64_t time_ms; uint64_t time_us; uint64_t time_ns; int64_t time_sec; mm_heap_t timers; int timers_seq; }; void mm_clock_init(mm_clock_t *); void mm_clock_free(mm_clock_t *); void mm_clock_update(mm_clock_t *); void mm_clock_step(mm_clock_t *); int mm_clock_timer_add(mm_clock_t *, mm_timer_t *); int mm_clock_timer_del(mm_clock_t *, mm_timer_t *); mm_timer_t *mm_clock_timer_min(mm_clock_t *); static inline void mm_clock_reset(mm_clock_t *clock) { clock->time_cached = 0; } static inline void mm_timer_start(mm_clock_t *clock, mm_timer_t *timer) { if (!clock->active) { mm_clock_update(clock); } clock->active = 1; mm_clock_timer_add(clock, timer); } static inline void mm_timer_stop(mm_timer_t *timer) { if (!timer->active) { return; } mm_clock_t *clock = timer->clock; mm_clock_timer_del(clock, timer); if (mm_heap_size(&clock->timers) == 0) { clock->active = 0; } } odyssey-1.6.0-rc1/sources/include/machinarium/compression.h000066400000000000000000000006751526064202200240240ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include void mm_compression_free(mm_io_t *io); static inline int mm_compression_is_active(mm_io_t *io) { return io->zpq_stream != NULL; } int mm_compression_writev(mm_io_t *io, struct iovec *iov, int n, size_t *processed); int mm_compression_read_pending(mm_io_t *io); int mm_compression_write_pending(mm_io_t *io); odyssey-1.6.0-rc1/sources/include/machinarium/cond.h000066400000000000000000000022521526064202200223770ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include typedef struct mm_cond mm_cond_t; enum { MM_COND_WAIT_FAIL = -1, MM_COND_WAIT_OK = 0, MM_COND_WAIT_OK_PROPAGATED = 1 }; typedef struct { mm_list_t link; mm_call_t call; /* is signal came directly or as for propagated cond? */ int propagated; #ifndef NDEBUG /* * must always be used from one machine */ mm_scheduler_t *owner; #endif } mm_cond_awaiter_t; struct mm_cond { mm_list_t awaiters; mm_cond_t *propagate; #ifndef NDEBUG /* * must always be used from one machine */ mm_scheduler_t *owner; #endif }; static inline void mm_cond_init(mm_cond_t *cond) { cond->propagate = NULL; mm_list_init(&cond->awaiters); #ifndef NDEBUG cond->owner = NULL; #endif } void mm_cond_signal(mm_cond_t *cond); void mm_cond_propagate(mm_cond_t *src, mm_cond_t *dst); /* * spirious wakeups are possible * (when awaiting one cond from several coroutines) */ int mm_cond_wait(mm_cond_t *cond, uint32_t time_ms); odyssey-1.6.0-rc1/sources/include/machinarium/context.h000066400000000000000000000026401526064202200231410ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #ifdef USE_UCONTEXT #include #endif #include typedef void (*mm_context_function_t)(void *); typedef struct mm_context mm_context_t; struct mm_context { #ifdef USE_UCONTEXT ucontext_t uctx; #else void **sp; #endif #ifdef HAVE_TSAN void *tsan_fiber; int destroying; mm_context_t *exit_from; #endif }; void mm_context_create(mm_context_t *, mm_contextstack_t *, mm_context_function_t, void *); void mm_context_destroy(mm_context_t *); void mm_context_runner(void); /* * if the build is with TSAN, then the wrapper function must be used * otherwise, just make proxy inlined function to impl * * mm_context_swap possibly adds TSAN wrapper and then calls * mm_context_swap_private, which select ucontext if must */ #ifndef USE_UCONTEXT void mm_context_swap_impl(mm_context_t *, mm_context_t *); #endif static inline void mm_context_swap_private(mm_context_t *current, mm_context_t *new) { #ifdef USE_UCONTEXT swapcontext(¤t->uctx, &new->uctx); #else mm_context_swap_impl(current, new); #endif /* USE_UCONTEXT */ } #ifdef HAVE_TSAN void mm_context_swap(mm_context_t *current, mm_context_t *new); #else static inline void mm_context_swap(mm_context_t *current, mm_context_t *new) { mm_context_swap_private(current, new); } #endif /* HAVE_TSAN */ odyssey-1.6.0-rc1/sources/include/machinarium/context_stack.h000066400000000000000000000006461526064202200243320ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_contextstack mm_contextstack_t; struct mm_contextstack { char *pointer; size_t size; size_t size_guard; #ifdef HAVE_VALGRIND int valgrind_stack; #endif }; int mm_contextstack_create(mm_contextstack_t *, size_t, size_t); void mm_contextstack_free(mm_contextstack_t *); odyssey-1.6.0-rc1/sources/include/machinarium/coroutine.h000066400000000000000000000024301526064202200234610ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #define MM_COROUTINE_MAX_NAME_LEN 15 typedef struct mm_coroutine mm_coroutine_t; typedef struct mm_coroutine_cache mm_coroutine_cache_t; typedef void (*mm_function_t)(void *arg); typedef enum { MM_CNEW, MM_CREADY, MM_CACTIVE, MM_CFREE } mm_coroutinestate_t; struct mm_coroutine { uint64_t id; mm_coroutinestate_t state; int cancel; int errno_; mm_function_t function; void *function_arg; mm_contextstack_t stack; mm_context_t context; mm_coroutine_t *resume; mm_coroutine_cache_t *origin_cache; void *call_ptr; mm_list_t joiners; mm_list_t link_join; mm_list_t link; char name[MM_COROUTINE_MAX_NAME_LEN + 1]; #ifdef MM_MEM_PROF uint64_t allocated_bytes; uint64_t freed_bytes; #endif }; mm_coroutine_t *mm_coroutine_allocate(int, int); void mm_coroutine_init(mm_coroutine_t *); void mm_coroutine_free(mm_coroutine_t *); void mm_coroutine_cancel(mm_coroutine_t *); void mm_coroutine_set_name(mm_coroutine_t *, const char *); const char *mm_coroutine_get_name(mm_coroutine_t *); static inline int mm_coroutine_is_cancelled(mm_coroutine_t *coroutine) { return coroutine->cancel; } odyssey-1.6.0-rc1/sources/include/machinarium/coroutine_cache.h000066400000000000000000000012311526064202200246020ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_coroutine_cache mm_coroutine_cache_t; struct mm_coroutine_cache { int stack_size; int stack_size_guard; mm_list_t list; int count_free; int count_total; int limit; }; void mm_coroutine_cache_init(mm_coroutine_cache_t *, int, int, int); void mm_coroutine_cache_free(mm_coroutine_cache_t *); void mm_coroutine_cache_stat(mm_coroutine_cache_t *, uint64_t *, uint64_t *); mm_coroutine_t *mm_coroutine_cache_pop(mm_coroutine_cache_t *); void mm_coroutine_cache_push(mm_coroutine_cache_t *, mm_coroutine_t *); odyssey-1.6.0-rc1/sources/include/machinarium/dns.h000066400000000000000000000015601526064202200222410ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include typedef struct mm_addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct mm_addrinfo *ai_next; atomic_uint_fast64_t refs; } mm_addrinfo_t; typedef struct { char addr[NI_MAXHOST]; char service[NI_MAXSERV]; int ai_family; int ai_socktype; int ai_protocol; int gai_rc; mm_addrinfo_t *ai; uint64_t valid_until_ms; mm_list_t link; } mm_dns_cache_entry_t; void mm_free_dns_cache_entry(mm_dns_cache_entry_t *e); int mm_getaddrinfo(char *addr, char *service, struct addrinfo *hints, mm_addrinfo_t **res, uint32_t time_ms); void mm_freeaddrinfo(mm_addrinfo_t *ai); odyssey-1.6.0-rc1/sources/include/machinarium/ds/000077500000000000000000000000001526064202200217105ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/machinarium/ds/heap.h000066400000000000000000000015421526064202200230000ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. * * yet another heap implementation * this one is NOT thread-safe and is highly inspired by libuv implementation */ #include typedef struct mm_heap_node { struct mm_heap_node *left; struct mm_heap_node *right; struct mm_heap_node *parent; } mm_heap_node_t; /* non-zero if a < b */ typedef int (*mm_heap_cmp_fn)(const mm_heap_node_t *a, const mm_heap_node_t *b); typedef struct { mm_heap_node_t *min; mm_heap_cmp_fn cmp; size_t size; } mm_heap_t; void mm_heap_init(mm_heap_t *h, mm_heap_cmp_fn cmp); mm_heap_node_t *mm_heap_min(mm_heap_t *h); size_t mm_heap_size(mm_heap_t *h); void mm_heap_push(mm_heap_t *h, mm_heap_node_t *node); void mm_heap_remove(mm_heap_t *h, mm_heap_node_t *node); mm_heap_node_t *mm_heap_pop(mm_heap_t *h); int mm_heap_validate(mm_heap_t *h); odyssey-1.6.0-rc1/sources/include/machinarium/ds/hm.h000066400000000000000000000050151526064202200224660ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * yet another hashmap implementation * this one is concurrent variant ready machinarium engine */ #include #include #include #include typedef uint64_t mm_hash_t; typedef struct mm_hashmap mm_hashmap_t; typedef int (*mm_hm_key_cmp_fn)(const void *key1, const void *key2); typedef mm_hash_t (*mm_hm_key_hash_fn)(const void *key); typedef void (*mm_hm_key_dtor_fn)(void *key); typedef void (*mm_hm_value_dtor_fn)(void *value); typedef int (*mm_hm_key_copy_fn)(void *dst, const void *src); typedef struct { mm_list_t link; mm_hash_t hash; uint8_t keyval[]; } mm_hashmap_kvp_t; typedef int (*mm_hm_kvp_cb_fn)(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp, void **argv); typedef struct { /* mm_hashmap_kvp_t[] */ mm_list_t kvps; mm_mutex_t *mu; } mm_hashmap_bucket_t; typedef struct { mm_hashmap_kvp_t *kvp; mm_hashmap_bucket_t *bucket; int found; } mm_hashmap_keylock_t; typedef enum { MM_HASHMAP_CREATE = 1 << 0, } mm_hashmap_lockkey_flags_t; struct mm_hashmap { size_t keysz; size_t valsz; size_t keyoff; size_t valoff; size_t kvpsize; mm_hm_key_cmp_fn kcmp; mm_hm_key_hash_fn khash; mm_hm_key_dtor_fn kdtor; mm_hm_value_dtor_fn vdtor; mm_hm_key_copy_fn kcopy; size_t nbuckets; mm_hashmap_bucket_t *buckets; size_t nlocks; mm_mutex_t *locks; }; void *mm_hashmap_kvp_key(const mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp); void *mm_hashmap_kvp_val(const mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp); const void *mm_hashmap_kvp_key_const(const mm_hashmap_t *hm, const mm_hashmap_kvp_t *kvp); const void *mm_hashmap_kvp_val_const(const mm_hashmap_t *hm, const mm_hashmap_kvp_t *kvp); mm_hashmap_t *mm_hashmap_create(size_t nbuckets, size_t nlocks, size_t keysz, size_t valsz, mm_hm_key_cmp_fn kcmp, mm_hm_key_hash_fn khash, mm_hm_key_dtor_fn kdtor, mm_hm_value_dtor_fn vdtor, mm_hm_key_copy_fn kcopy); void mm_hashmap_free(mm_hashmap_t *hm); void mm_hashmap_clear(mm_hashmap_t *hm); int mm_hashmap_foreach(mm_hashmap_t *hm, mm_hm_kvp_cb_fn cb, void **argv); int mm_hashmap_lock_key(mm_hashmap_t *hm, mm_hashmap_keylock_t *klock, const void *key, int flags /* see mm_hashmap_lockkey_flags_t */); void mm_hashmap_unlock_key(mm_hashmap_t *hm, mm_hashmap_keylock_t *klock); void mm_hashmap_remove(mm_hashmap_t *hm, mm_hashmap_keylock_t *klock); /* seed is optional, can be zero */ mm_hash_t mm_xxh64_hash(const void *data, size_t size, uint64_t seed); odyssey-1.6.0-rc1/sources/include/machinarium/ds/intrusive/000077500000000000000000000000001526064202200237405ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/machinarium/ds/intrusive/lf-stack.h000066400000000000000000000022751526064202200256230ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. * * lock-free intrusive stack (Treiber stack) with ABA protection. * * nb: on x86-64 with -mcx16 the compiled code requires a CPU that * supports cmpxchg16b (all processors since ~2008). Running on an * older CPU will SIGILL. This is a deployment assumption, not a runtime * check. */ #include typedef struct mm_lf_stack_entry { struct mm_lf_stack_entry *prev; } mm_lf_stack_entry_t; #if defined(__SIZEOF_INT128__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) /* lock-free path: 128-bit tagged pointer via __sync builtins */ #define MM_LF_STACK_TAGGED 1 typedef __int128_t mm_int128; typedef struct { uintptr_t ptr; uint64_t tag; } mm_lf_stack_head_t; typedef struct { mm_int128 raw; } mm_lf_stack_t; #else /* fallback path: spinlock */ #define MM_LF_STACK_TAGGED 0 #include typedef struct { mm_lf_stack_entry_t *head; mm_spinlock_t lock; } mm_lf_stack_t; #endif void mm_lf_stack_init(mm_lf_stack_t *st); void mm_lf_stack_destroy(mm_lf_stack_t *st); int mm_lf_stack_push(mm_lf_stack_t *st, mm_lf_stack_entry_t *e); mm_lf_stack_entry_t *mm_lf_stack_pop(mm_lf_stack_t *st); odyssey-1.6.0-rc1/sources/include/machinarium/ds/intrusive/mpsc_queue.h000066400000000000000000000040141526064202200262560ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. * * lock-free intrusive multi-producer single-consumer (!!!) queue. */ #include #include typedef struct mm_mpsc_node { _Atomic(struct mm_mpsc_node *) next; } mm_mpsc_node_t; typedef struct { /* consumer-only actually, but let it be atomic anyway */ _Atomic(mm_mpsc_node_t *) head; _Atomic(mm_mpsc_node_t *) tail; mm_mpsc_node_t stub; /* approximate queue size */ _Atomic(size_t) size; } mm_mpsc_queue_t; static inline void mm_mpsc_queue_init(mm_mpsc_queue_t *q) { atomic_init(&q->stub.next, NULL); atomic_init(&q->head, &q->stub); atomic_init(&q->tail, &q->stub); atomic_init(&q->size, 0); } static inline size_t mm_mpsc_queue_size(mm_mpsc_queue_t *q) { return atomic_load_explicit(&q->size, memory_order_relaxed); } static inline int mm_mpsc_queue_empty(mm_mpsc_queue_t *q) { mm_mpsc_node_t *head = atomic_load_explicit(&q->head, memory_order_relaxed); mm_mpsc_node_t *next = atomic_load_explicit(&head->next, memory_order_acquire); return next == NULL; } /* * Pop one element. Returns NULL if queue is empty or a producer is * mid-push (the window between atomic_exchange and prev->next = n). */ static inline mm_mpsc_node_t *mm_mpsc_queue_pop(mm_mpsc_queue_t *q) { mm_mpsc_node_t *head = atomic_load_explicit(&q->head, memory_order_relaxed); mm_mpsc_node_t *next = atomic_load_explicit(&head->next, memory_order_acquire); if (next == NULL) { /* nb: may be incomplete push, ignore anyway */ return NULL; } atomic_store_explicit(&q->head, next, memory_order_relaxed); atomic_fetch_sub_explicit(&q->size, 1, memory_order_relaxed); return next; } static inline size_t mm_mpsc_queue_push(mm_mpsc_queue_t *q, mm_mpsc_node_t *n) { atomic_store_explicit(&n->next, NULL, memory_order_relaxed); mm_mpsc_node_t *prev = atomic_exchange_explicit(&q->tail, n, memory_order_acq_rel); atomic_store_explicit(&prev->next, n, memory_order_release); return atomic_fetch_add_explicit(&q->size, 1, memory_order_relaxed); } odyssey-1.6.0-rc1/sources/include/machinarium/ds/queue.h000066400000000000000000000020661526064202200232110ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * yet another concurrent queue implementation */ #include #include #include typedef void (*mm_queue_dtor_fn)(void *val); typedef struct { mm_spinlock_t lock; size_t cap; size_t mask; size_t elsize; size_t size; size_t head; /* index */ size_t tail; /* index */ mm_queue_dtor_fn dtor; uint8_t *buf; } mm_queue_t; int mm_queue_init(mm_queue_t *queue, size_t capacity, size_t elsize, mm_queue_dtor_fn dtor); void mm_queue_destroy(mm_queue_t *queue); int mm_queue_push(mm_queue_t *queue, const void *val); /* return -1 if no place for value, new number of elements otherwise */ int mm_queue_push_extended(mm_queue_t *queue, const void *val); int mm_queue_pop(mm_queue_t *queue, void *val); size_t mm_queue_pop_batch(mm_queue_t *queue, void *dst, size_t max); size_t mm_queue_size(mm_queue_t *queue); #define mm_queue_init_types(queue, cap, elsize, type, dtor) \ mm_queue_init((queue), (cap), (elsize), _Alignof(type), (dtor)) odyssey-1.6.0-rc1/sources/include/machinarium/ds/vector.h000066400000000000000000000014731526064202200233700ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * yet another impl for dynamic size array */ #include #include typedef void (*mm_vector_element_dtor_fn)(void *element); typedef struct { uint8_t *elements; mm_vector_element_dtor_fn eldtor; size_t elsize; size_t size; size_t capacity; } mm_vector_t; void mm_vector_init(mm_vector_t *vec, size_t elsize, mm_vector_element_dtor_fn eldtor); void mm_vector_destroy(mm_vector_t *vec); size_t mm_vector_size(const mm_vector_t *vec); int mm_vector_empty(const mm_vector_t *vec); void mm_vector_clear(mm_vector_t *vec); void *mm_vector_get(mm_vector_t *vec, size_t idx); void *mm_vector_back(mm_vector_t *vec); int mm_vector_append(mm_vector_t *vec, const void *val); int mm_vector_shrink_to_fit(mm_vector_t *vec); odyssey-1.6.0-rc1/sources/include/machinarium/ds/vrb.h000066400000000000000000000032361526064202200226560ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include /* * yet another virtual ring buffer - ring buffer that can be used * for every cases, that works with straight bytes array (like read syscall) */ typedef struct { uint8_t *data; size_t capacity; size_t rpos; size_t wpos; } mm_virtual_rbuf_t; mm_virtual_rbuf_t *mm_virtual_rbuf_create(size_t capacity); void mm_virtual_rbuf_free(mm_virtual_rbuf_t *vrb); size_t mm_virtual_rbuf_capacity(const mm_virtual_rbuf_t *vrb); size_t mm_virtual_rbuf_size(const mm_virtual_rbuf_t *vrb); size_t mm_virtual_rbuf_free_size(const mm_virtual_rbuf_t *vrb); /* zero copy interfaces for syscalls */ struct iovec mm_virtual_rbuf_write_begin(const mm_virtual_rbuf_t *vrb); void mm_virtual_rbuf_write_commit(mm_virtual_rbuf_t *vrb, size_t len); struct iovec mm_virtual_rbuf_read_begin(const mm_virtual_rbuf_t *vrb); void mm_virtual_rbuf_read_commit(mm_virtual_rbuf_t *vrb, size_t len); /* classic rbuf interface */ size_t mm_virtual_rbuf_read(mm_virtual_rbuf_t *vrb, void *out, size_t count); size_t mm_virtual_rbuf_drain(mm_virtual_rbuf_t *vrb, size_t count); size_t mm_virtual_rbuf_write(mm_virtual_rbuf_t *vrb, const void *data, size_t count); typedef struct { mm_virtual_rbuf_t **rbufs; size_t max; size_t count; } mm_virtual_rbuf_cache_t; int mm_virtual_rbuf_cache_init(mm_virtual_rbuf_cache_t *cache, size_t max_bufs); void mm_virtual_rbuf_cache_destroy(mm_virtual_rbuf_cache_t *cache); mm_virtual_rbuf_t *mm_virtual_rbuf_cache_get(mm_virtual_rbuf_cache_t *cache); void mm_virtual_rbuf_cache_put(mm_virtual_rbuf_cache_t *cache, mm_virtual_rbuf_t *vrb); odyssey-1.6.0-rc1/sources/include/machinarium/epoll.h000066400000000000000000000002141526064202200225630ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include extern mm_pollif_t mm_epoll_if; odyssey-1.6.0-rc1/sources/include/machinarium/event.h000066400000000000000000000005651526064202200226020ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_event mm_event_t; typedef enum { MM_EVENT_NONE, MM_EVENT_WAIT, MM_EVENT_READY, MM_EVENT_ACTIVE } mm_eventstate_t; struct mm_event { mm_eventstate_t state; mm_call_t call; void *event_mgr; mm_list_t link; }; odyssey-1.6.0-rc1/sources/include/machinarium/event_mgr.h000066400000000000000000000016051526064202200234430ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include typedef struct mm_eventmgr_t mm_eventmgr_t; struct mm_eventmgr_t { mm_fd_t fd; #if !defined(__linux__) int wakeup_fd; #endif mm_sleeplock_t lock; mm_list_t list_ready; mm_list_t list_wait; int count_ready; int count_wait; }; int mm_eventmgr_init(mm_eventmgr_t *, mm_loop_t *); void mm_eventmgr_free(mm_eventmgr_t *, mm_loop_t *); void mm_eventmgr_add(mm_eventmgr_t *, mm_event_t *); int mm_eventmgr_wait(mm_eventmgr_t *, mm_event_t *, uint32_t); int mm_eventmgr_signal(mm_event_t *); void mm_eventmgr_wakeup(int); static inline int mm_eventmgr_wakeup_fd(mm_eventmgr_t *mgr) { #if defined(__linux__) return mgr->fd.fd; #else return mgr->wakeup_fd; #endif } odyssey-1.6.0-rc1/sources/include/machinarium/eventfd.h000066400000000000000000000013041526064202200231040ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct { mm_fd_t handle; #if !defined(__linux__) int write_fd; #endif mm_cond_t cond; int attached; } mm_eventfd_t; int mm_eventfd_init(mm_eventfd_t *efd); void mm_eventfd_destroy(mm_eventfd_t *efd); int mm_eventfd_attach(mm_eventfd_t *efd); int mm_eventfd_detach(mm_eventfd_t *efd); int mm_eventfd_read(mm_eventfd_t *efd, uint32_t timeout_ms); int mm_eventfd_write(mm_eventfd_t *efd, uint32_t timeout_ms); void mm_eventfd_peer_to(mm_eventfd_t *efd, mm_io_t *io); void mm_eventfd_remove_peer_to(mm_eventfd_t *efd, mm_io_t *io); odyssey-1.6.0-rc1/sources/include/machinarium/fd.h000066400000000000000000000010121526064202200220360ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* machinarium file descriptor */ typedef struct mm_fd mm_fd_t; enum { MM_R = (1 << 0), MM_W = (1 << 1), MM_ERR = (1 << 2), MM_CLOSE = (1 << 3) }; typedef void (*mm_fd_callback_t)(mm_fd_t *); struct mm_fd { int fd; int mask; mm_fd_callback_t on_read; void *on_read_arg; mm_fd_callback_t on_write; void *on_write_arg; mm_fd_callback_t on_err; void *on_err_arg; mm_fd_callback_t on_close; void *on_close_arg; int last_event; }; odyssey-1.6.0-rc1/sources/include/machinarium/idle.h000066400000000000000000000003371526064202200223730ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ typedef struct mm_idle mm_idle_t; typedef int (*mm_idle_callback_t)(mm_idle_t *); struct mm_idle { mm_idle_callback_t callback; void *arg; }; odyssey-1.6.0-rc1/sources/include/machinarium/io.h000066400000000000000000000061661526064202200220730ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include typedef struct mm_tls mm_tls_t; typedef struct mm_tls_ctx mm_tls_ctx_t; typedef enum { MM_TLS_NONE, MM_TLS_PEER, MM_TLS_PEER_STRICT } mm_tlsverify_t; struct mm_tls { mm_tlsverify_t verify; char *server; char *protocols; char *ca_path; char *ca_file; char *cert_file; char *key_file; }; struct mm_tls_ctx { mm_tlsverify_t verify; char *protocols; char *cert_file; char *key_file; char *ca_file; char *ca_path; uint64_t epoch; SSL_CTX *tls_ctx; mm_tls_ctx_t *next; }; enum { MM_IO_WANT_READ = 1 << 0, MM_IO_WANT_WRITE = 1 << 1, }; struct mm_io { int fd; mm_fd_t handle; int attached; int is_unix_socket; int is_eventfd; int opt_nodelay; int opt_cloexec; /* tcp keepalive */ int opt_keepalive; int opt_keepalive_delay; int opt_keepalive_interval; int opt_keepalive_probes; int opt_keepalive_usr_timeout; /* tls */ mm_tls_t *tls; SSL *tls_ssl; int tls_error; char tls_error_msg[128]; /* connect */ int connected; /* accept */ int accepted; int accept_listen; /* io */ mm_cond_t cond; int errored; int error; mm_call_t call; /* compression */ mm_zpq_stream_t *zpq_stream; uint64_t deadline_ms; int want; int read_ready; }; mm_io_t *mm_io_create(void); void mm_io_free(mm_io_t *); int mm_io_socket_set(mm_io_t *, int); int mm_io_socket(mm_io_t *, struct sockaddr *); ssize_t mm_io_write(mm_io_t *, const void *, size_t); ssize_t mm_io_read(mm_io_t *, void *, size_t); int mm_io_format_socket_addr(mm_io_t *, char *, size_t); int mm_io_read_pending(mm_io_t *); void mm_io_set_peer(mm_io_t *io, mm_io_t *peer); void mm_io_remove_peer(mm_io_t *io, mm_io_t *peer); int mm_io_wait(mm_io_t *io, uint32_t timeout_ms); int mm_io_verify(mm_io_t *, char *common_name); int mm_io_format_socket_addr(mm_io_t *io, char *buf, size_t buflen); int mm_io_attach(mm_io_t *); int mm_io_detach(mm_io_t *); int mm_io_is_tls(mm_io_t *); char *mm_io_error(mm_io_t *); int mm_io_fd(mm_io_t *); int mm_io_set_nodelay(mm_io_t *, int enable); int mm_io_set_keepalive(mm_io_t *, int enable, int delay, int interval, int probes, int usr_timeout); int mm_io_set_nolinger(mm_io_t *io); int mm_io_advice_keepalive_usr_timeout(int delay, int interval, int probes); int mm_io_set_tls(mm_io_t *, machine_tls_t *, uint32_t); int mm_io_set_compression(mm_io_t *, char algorithm); int mm_io_connect(mm_io_t *, struct sockaddr *, uint32_t time_ms); int mm_io_connected(mm_io_t *); int mm_io_bind(mm_io_t *, struct sockaddr *, int); int mm_io_accept(mm_io_t *, mm_io_t **, int backlog, int attach, uint32_t time_ms); int mm_io_close(mm_io_t *); int mm_io_shutdown(mm_io_t *); int mm_io_set_tls(mm_io_t *obj, machine_tls_t *tls, uint32_t timeout); int mm_io_last_event(mm_io_t *io); int mm_io_want_read(mm_io_t *io); int mm_io_want_write(mm_io_t *io); int mm_io_read_ready(mm_io_t *io); /* now + timeout_ms */ void mm_io_set_deadline(mm_io_t *io, uint32_t timeout_ms); int mm_io_wait_deadline(mm_io_t *io); int mm_io_poll(mm_io_t *io); odyssey-1.6.0-rc1/sources/include/machinarium/iov.h000066400000000000000000000016441526064202200222550ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * Structure for scatter/gather I/O * */ #include static inline int mm_iov_size_of(const struct iovec *iov, int count) { int size = 0; while (count > 0) { size += iov->iov_len; iov++; count--; } return size; } static inline void mm_iovcpy(char *dest, const struct iovec *iov, int count) { const struct iovec *pos = iov; int pos_dest = 0; int n = count; while (n > 0) { memcpy(dest + pos_dest, pos->iov_base, pos->iov_len); pos_dest += pos->iov_len; pos++; n--; } } static inline void mm_iovncpy(char *dest, const struct iovec *vec, int max, int count) { /* same as mm_iovcpy but copy no more than max bytes */ while (count > 0 && max > 0) { int len = (int)vec->iov_len; if (len > max) { len = max; } memcpy(dest, vec->iov_base, len); max -= len; dest += len; --count; ++vec; } } odyssey-1.6.0-rc1/sources/include/machinarium/kqueue.h000066400000000000000000000002151526064202200227500ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include extern mm_pollif_t mm_kqueue_if; odyssey-1.6.0-rc1/sources/include/machinarium/list.h000066400000000000000000000024441526064202200224320ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ typedef struct mm_list mm_list_t; struct mm_list { mm_list_t *next, *prev; }; static inline void mm_list_init(mm_list_t *list) { list->next = list->prev = list; } static inline void mm_list_append(mm_list_t *list, mm_list_t *node) { node->next = list; node->prev = list->prev; node->prev->next = node; node->next->prev = node; } static inline void mm_list_unlink(mm_list_t *node) { node->prev->next = node->next; node->next->prev = node->prev; } static inline void mm_list_push(mm_list_t *list, mm_list_t *node) { node->next = list->next; node->prev = list; node->prev->next = node; node->next->prev = node; } static inline mm_list_t *mm_list_pop(mm_list_t *list) { register mm_list_t *pop = list->next; mm_list_unlink(pop); return pop; } static inline mm_list_t *mm_list_pop_back(mm_list_t *list) { register mm_list_t *pop = list->prev; mm_list_unlink(pop); return pop; } static inline int mm_list_empty(mm_list_t *list) { return list->next == list && list->prev == list; } #define mm_list_foreach(H, I) for (I = (H)->next; I != H; I = (I)->next) #define mm_list_foreach_safe(H, I, N) \ for (I = (H)->next; I != H && (N = I->next); I = N) #define mm_list_peek(H, type) mm_container_of((H).next, type, link) odyssey-1.6.0-rc1/sources/include/machinarium/loop.h000066400000000000000000000024201526064202200224220ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_loop mm_loop_t; struct mm_loop { mm_clock_t clock; mm_idle_t idle; mm_poll_t *poll; }; int mm_loop_init(mm_loop_t *); int mm_loop_shutdown(mm_loop_t *); int mm_loop_step(mm_loop_t *); static inline void mm_loop_set_idle(mm_loop_t *loop, mm_idle_callback_t cb, void *arg) { loop->idle.callback = cb; loop->idle.arg = arg; } static inline int mm_loop_add(mm_loop_t *loop, mm_fd_t *fd, mm_fd_callback_t on_read, void *on_read_arg, mm_fd_callback_t on_write, void *on_write_arg, mm_fd_callback_t on_err, void *on_err_arg, mm_fd_callback_t on_close, void *on_close_arg) { return loop->poll->iface->add(loop->poll, fd, on_read, on_read_arg, on_write, on_write_arg, on_err, on_err_arg, on_close, on_close_arg); } static inline int mm_loop_add_ro(mm_loop_t *loop, mm_fd_t *fd, mm_fd_callback_t on_read, void *on_read_arg) { return mm_loop_add(loop, fd, on_read, on_read_arg, NULL, NULL, NULL, NULL, NULL, NULL); } static inline int mm_loop_delete(mm_loop_t *loop, mm_fd_t *fd) { return loop->poll->iface->del(loop->poll, fd); } odyssey-1.6.0-rc1/sources/include/machinarium/lrand48.h000066400000000000000000000001511526064202200227240ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ void mm_lrand48_seed(void); odyssey-1.6.0-rc1/sources/include/machinarium/machinarium.h000066400000000000000000000254651526064202200237640ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * ! ! * ! THIS HEADER IS UNDER RECONSTRUCTION ! * ! CONSIDER INCLUDING SPECIFIC machinarium/... files ! * ! ! * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include "zpq_stream.h" #include "bind.h" #include #include #include #include "macro.h" #include "channel_limit.h" #if __GNUC__ >= 4 #define MACHINE_API __attribute__((visibility("default"))) #else #define MACHINE_API #endif typedef void (*machine_coroutine_t)(void *arg); #define mm_yield machine_sleep(0); #define MM_CERT_HASH_LEN EVP_MAX_MD_SIZE /* library handles */ typedef struct mm_io mm_io_t; typedef struct machine_cond_private machine_cond_t; typedef struct machine_msg_private machine_msg_t; typedef struct machine_channel_private machine_channel_t; typedef struct machine_tls_private machine_tls_t; typedef struct machine_iov_private machine_iov_t; typedef struct machine_wait_flag machine_wait_flag_t; typedef struct machine_wait_group machine_wait_group_t; typedef struct machine_ring_buffer machine_ring_buffer_t; /* configuration */ MACHINE_API void machinarium_set_stack_size(int size); MACHINE_API void machinarium_set_system_stack_size(int size); MACHINE_API void machinarium_set_pool_size(int size); MACHINE_API void machinarium_set_coroutine_cache_size(int size); MACHINE_API void machinarium_set_msg_cache_gc_size(int size); MACHINE_API void machinarium_set_msg_cache_gc_count(int count); MACHINE_API void machinarium_set_dns_ttl_ms(int ttl_ms); /* main */ MACHINE_API int machinarium_init(void); MACHINE_API void machinarium_free(void); /* machine control */ MACHINE_API int64_t machine_create(char *name, machine_coroutine_t, void *arg); MACHINE_API void machine_stop_current(void); MACHINE_API int machine_active(void); MACHINE_API uint64_t machine_self(void); MACHINE_API void **machine_thread_private(void); MACHINE_API int machine_wait(uint64_t machine_id); MACHINE_API int machine_stop(uint64_t machine_id); /* time */ MACHINE_API uint64_t machine_time_ms(void); MACHINE_API uint64_t machine_time_us(void); MACHINE_API int64_t machine_timeofday_sec(void); MACHINE_API void machine_stat(uint64_t *coroutine_count, uint64_t *coroutine_cache_count, uint64_t *msg_allocated, uint64_t *msg_cache_count, uint64_t *msg_cache_gc_count, uint64_t *msg_cache_size); /* signals */ MACHINE_API int machine_signal_init(sigset_t *, sigset_t *); MACHINE_API int machine_signal_wait(uint32_t time_ms); /* coroutine */ MACHINE_API int64_t machine_coroutine_create(machine_coroutine_t, void *arg); MACHINE_API int64_t machine_coroutine_create_named(machine_coroutine_t, void *, const char *); MACHINE_API int64_t machine_coroutine_create_system(machine_coroutine_t, void *, const char *); MACHINE_API void machine_sleep(uint32_t time_ms); MACHINE_API int machine_join(uint64_t coroutine_id); MACHINE_API int machine_cancel(uint64_t coroutine_id); MACHINE_API int machine_cancelled(void); MACHINE_API int machine_timedout(void); MACHINE_API int machine_errno(void); MACHINE_API int machine_errno_retryable(int errno_); MACHINE_API int machine_accept_errno_retryable(int errno_); MACHINE_API const char *machine_coroutine_get_name(uint64_t coroutine_id); /* condition */ MACHINE_API machine_cond_t *machine_cond_create(void); MACHINE_API void machine_cond_free(machine_cond_t *); MACHINE_API void machine_cond_propagate(machine_cond_t *, machine_cond_t *); MACHINE_API void machine_cond_signal(machine_cond_t *); MACHINE_API int machine_cond_wait(machine_cond_t *, uint32_t time_ms); /* msg */ MACHINE_API machine_msg_t *machine_msg_create(int reserve); MACHINE_API machine_msg_t *machine_msg_create_or_advance(machine_msg_t *, int size); MACHINE_API void machine_msg_free(machine_msg_t *); MACHINE_API void machine_msg_free_safe(machine_msg_t *obj); MACHINE_API void machine_msg_set_type(machine_msg_t *, int type); MACHINE_API int machine_msg_type(machine_msg_t *); MACHINE_API void *machine_msg_data(machine_msg_t *); MACHINE_API int machine_msg_size(machine_msg_t *); MACHINE_API void machine_msg_reset(machine_msg_t *); MACHINE_API int machine_msg_shrink(machine_msg_t *, int size); MACHINE_API int machine_msg_write(machine_msg_t *, void *buf, int size); MACHINE_API machine_msg_t *machine_msg_copy(machine_msg_t *msg); MACHINE_API struct iovec machine_msg_iovec(machine_msg_t *msg); /* channel */ MACHINE_API machine_channel_t *machine_channel_create(void); MACHINE_API void machine_channel_free(machine_channel_t *); MACHINE_API void machine_channel_assign_limit_policy(machine_channel_t *obj, int limit, mm_channel_limit_policy_t policy); MACHINE_API mm_retcode_t machine_channel_write(machine_channel_t *, machine_msg_t *); MACHINE_API machine_msg_t *machine_channel_read(machine_channel_t *, uint32_t time_ms); MACHINE_API machine_msg_t *machine_channel_read_back(machine_channel_t *, uint32_t time_ms); MACHINE_API size_t machine_channel_get_size(machine_channel_t *chan); /* tls */ MACHINE_API machine_tls_t *machine_tls_create(void); MACHINE_API void machine_tls_free(machine_tls_t *); MACHINE_API int machine_tls_set_verify(machine_tls_t *, char *); MACHINE_API int machine_tls_set_server(machine_tls_t *, char *); MACHINE_API int machine_tls_set_protocols(machine_tls_t *, char *); MACHINE_API int machine_tls_set_ca_path(machine_tls_t *, char *); MACHINE_API int machine_tls_set_ca_file(machine_tls_t *, char *); MACHINE_API int machine_tls_set_cert_file(machine_tls_t *, char *); MACHINE_API int machine_tls_set_key_file(machine_tls_t *, char *); MACHINE_API void machine_tls_cache_invalidate(void); /* dns */ MACHINE_API int machine_getsockname(mm_io_t *, struct sockaddr *, int *); MACHINE_API int machine_getpeername(mm_io_t *, struct sockaddr *, int *); /* iov */ MACHINE_API machine_iov_t *machine_iov_create(void); MACHINE_API void machine_iov_free(machine_iov_t *); MACHINE_API int machine_iov_add_pointer(machine_iov_t *, void *, int); MACHINE_API int machine_iov_add(machine_iov_t *, machine_msg_t *); MACHINE_API int machine_iov_pending(machine_iov_t *); MACHINE_API size_t machine_iov_inflight_size(machine_iov_t *iov); /* read */ MACHINE_API ssize_t machine_read_raw(mm_io_t *, void *, size_t); /* * Returns 1 if there are some bytes in io, that are ready to be read. */ MACHINE_API int machine_read_pending(mm_io_t *); MACHINE_API machine_msg_t *machine_read(mm_io_t *, size_t, uint32_t time_ms); /* write */ MACHINE_API ssize_t machine_write_raw(mm_io_t *, const void *, size_t, size_t *); MACHINE_API ssize_t machine_writev_raw(mm_io_t *, const struct iovec *iov, int iovcnt); MACHINE_API int machine_write(mm_io_t *, machine_msg_t *, uint32_t time_ms); MACHINE_API int machine_write_no_free(mm_io_t *, machine_msg_t *, uint32_t time_ms); /* lrand48 */ MACHINE_API void machine_lrand48_seed(void); MACHINE_API long int machine_lrand48(void); MACHINE_API double machine_erand48(unsigned short xseed[3]); /* stat/debug */ MACHINE_API int machine_io_sysfd(mm_io_t *); MACHINE_API int machine_io_mask(mm_io_t *); MACHINE_API int machine_io_mm_fd(mm_io_t *); /* tls cert hash */ MACHINE_API ssize_t machine_tls_cert_hash( mm_io_t *obj, unsigned char (*cert_hash)[MM_CERT_HASH_LEN], unsigned int *len); /* compression */ MACHINE_API char machine_compression_choose_alg(char *client_compression_algorithms); /* debug tools */ /* * note: backtrace functions are currently slow * if you want bt collection to be fast, impl should be rewritten */ MACHINE_API const char *machine_get_backtrace_string(void); MACHINE_API int machine_get_backtrace(void **entries, int max); /* wait group */ /* See pkg.go.dev/sync#WaitGroup for reference. There are two main differences between this wait group and the one from Go: 1. The user can pass a timeout in wait(). Obviously, there are no synchronization guarantees in case of wait() timing out. 2. There is a destroy() method that needs to be called to free the memory. The user should ensure that destroy() is called after all wait() calls are completed. Possible examples: 1. A simple scenario with two coroutines (or threads) and a single wait() call. The first thread creates a wait group, calls add(), spawns the second thread, calls wait() and calls destroy(). The second thread calls done(). See test_wait_group_simple.c for a code example. 2. A scenario with multiple wait() calls from different threads. Another wait group is used to wait (without timeout) until all wait() calls of the first wait group are completed. (This assumes that every thread that calls wait() on the first wait group also calls done() on the second wait group afterwards.) See test_wait_group_lifetime.c for a code example. */ MACHINE_API machine_wait_group_t *machine_wait_group_create(void); /* See notes above before using. */ MACHINE_API void machine_wait_group_destroy(machine_wait_group_t *group); MACHINE_API void machine_wait_group_add(machine_wait_group_t *group); /* This method exists for debug purposes only. It doesn't guarantee anything. */ MACHINE_API uint64_t machine_wait_group_count(machine_wait_group_t *group); MACHINE_API void machine_wait_group_done(machine_wait_group_t *group); MACHINE_API int machine_wait_group_wait(machine_wait_group_t *group, uint32_t timeout_ms); /* wait flag */ /* A wait flag is a simple synchronization primitive. It is one-shot, in other words, it can't be unset. It is safe to use from multiple workers. Both set and wait functions can be called multiple times. */ machine_wait_flag_t *machine_wait_flag_create(void); void machine_wait_flag_destroy(machine_wait_flag_t *flag); void machine_wait_flag_set(machine_wait_flag_t *flag); int machine_wait_flag_wait(machine_wait_flag_t *flag, uint32_t timeout_ms); /* ring buffer */ MACHINE_API machine_ring_buffer_t *machine_ring_buffer_create(size_t capacity); MACHINE_API void machine_ring_buffer_free(machine_ring_buffer_t *rbuf); MACHINE_API size_t machine_ring_buffer_read(machine_ring_buffer_t *rbuf, void *out, size_t count); MACHINE_API size_t machine_ring_buffer_drain(machine_ring_buffer_t *rbuf, size_t count); MACHINE_API size_t machine_ring_buffer_write(machine_ring_buffer_t *rbuf, const void *data, size_t count); MACHINE_API size_t machine_ring_buffer_size(const machine_ring_buffer_t *rbuf); MACHINE_API size_t machine_ring_buffer_capacity(const machine_ring_buffer_t *rbuf); MACHINE_API size_t machine_ring_buffer_available(const machine_ring_buffer_t *rbuf); MACHINE_API int machine_ring_buffer_is_full(const machine_ring_buffer_t *rbuf); #ifdef __cplusplus } #endif odyssey-1.6.0-rc1/sources/include/machinarium/machine.h000066400000000000000000000033241526064202200230610ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MM_MACHINE_MAX_ATEXIT 8 typedef struct { void (*fptr)(void *arg); void *arg; } mm_machine_atexit_entry_t; typedef struct mm_machine mm_machine_t; struct mm_machine { atomic_int online; uint64_t id; char *name; machine_coroutine_t main; void *main_arg; mm_thread_t thread; void *thread_global_private; void (*global_dtor)(void *gl); mm_scheduler_t scheduler; mm_signalmgr_t signal_mgr; mm_eventmgr_t event_mgr; mm_msgcache_t msg_cache; mm_coroutine_cache_t general_coroutine_cache; mm_coroutine_cache_t system_coroutine_cache; mm_loop_t loop; mm_list_t link; mm_machine_atexit_entry_t atexit[MM_MACHINE_MAX_ATEXIT]; size_t natexit; mm_wait_flag_t *join_flag; struct mm_tls_ctx *server_tls_ctx; struct mm_tls_ctx *client_tls_ctx; #ifdef MM_MEM_PROF uint64_t allocated_bytes; uint64_t freed_bytes; #endif }; extern __thread mm_machine_t *mm_self; static inline void mm_errno_set(int value) { mm_scheduler_current(&mm_self->scheduler)->errno_ = value; } static inline int mm_errno_get(void) { return mm_scheduler_current(&mm_self->scheduler)->errno_; } void mm_machine_atexit(void (*fun)(void *), void *arg); int machine_wait_nb(uint64_t machine_id); void machine_set_global_dtor(void (*dtor)(void *gl)); odyssey-1.6.0-rc1/sources/include/machinarium/machine_mgr.h000066400000000000000000000013301526064202200237210ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include typedef struct mm_machinemgr mm_machinemgr_t; struct mm_machinemgr { mm_spinlock_t lock; mm_list_t list; int count; uint64_t seq; }; void mm_machinemgr_init(mm_machinemgr_t *); void mm_machinemgr_free(mm_machinemgr_t *); int mm_machinemgr_count(mm_machinemgr_t *); void mm_machinemgr_add(mm_machinemgr_t *, mm_machine_t *); void mm_machinemgr_delete(mm_machinemgr_t *, mm_machine_t *); mm_machine_t *mm_machinemgr_delete_by_id(mm_machinemgr_t *, uint64_t); mm_machine_t *mm_machinemgr_find_by_id(mm_machinemgr_t *, uint64_t); odyssey-1.6.0-rc1/sources/include/machinarium/macro.h000066400000000000000000000014121526064202200225520ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #define mm_container_of(ptr, type, field) \ ((type *)((char *)(ptr) - __builtin_offsetof(type, field))) #define mm_cast(type, ptr) ((type)(ptr)) typedef int mm_retcode_t; #define MM_OK_RETCODE 0 #define MM_NOTOK_RETCODE -1 #ifndef IOV_MAX #define IOV_MAX __IOV_MAX #endif #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L #define MM_THREAD_LOCAL thread_local #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define MM_THREAD_LOCAL _Thread_local #elif defined(_MSC_VER) #define MM_THREAD_LOCAL __declspec(thread) #else #define MM_THREAD_LOCAL __thread #endif #define mm_likely(EXPR) __builtin_expect(!!(EXPR), 1) #define mm_unlikely(EXPR) __builtin_expect(!!(EXPR), 0) odyssey-1.6.0-rc1/sources/include/machinarium/memory.h000066400000000000000000000014241526064202200227640ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include void *mm_malloc(size_t size); void mm_free(void *ptr); void *mm_calloc(size_t nmemb, size_t size); void *mm_realloc(void *ptr, size_t size); char *mm_strdup(const char *s); char *mm_strndup(const char *s, size_t n); #ifdef MM_MEM_PROF static inline void *mm_malloc_crypto(size_t size, const char *file, int line) { (void)file; (void)line; return mm_malloc(size); } static inline void mm_free_crypto(void *ptr, const char *file, int line) { (void)file; (void)line; mm_free(ptr); } static inline void *mm_realloc_crypto(void *ptr, size_t size, const char *file, int line) { (void)file; (void)line; return mm_realloc(ptr, size); } #endif odyssey-1.6.0-rc1/sources/include/machinarium/mm.h000066400000000000000000000010121526064202200220560ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_config mm_config_t; typedef struct mm mm_t; struct mm_config { int page_size; int stack_size; int system_stack_size; int pool_size; int coroutine_cache_size; int msg_cache_gc_size; int msg_cache_gc_count; uint64_t dns_ttl_ms; }; struct mm { mm_config_t config; mm_machinemgr_t machine_mgr; mm_taskmgr_t task_mgr; }; extern mm_t machinarium; odyssey-1.6.0-rc1/sources/include/machinarium/msg.h000066400000000000000000000007171526064202200222460ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_msg mm_msg_t; struct mm_msg { uint16_t refs; uint64_t machine_id; int type; mm_buf_t data; mm_list_t link; }; static inline void mm_msg_init(mm_msg_t *msg, int type) { msg->refs = 0; msg->type = type; msg->machine_id = 0; mm_buf_init(&msg->data); mm_list_init(&msg->link); } odyssey-1.6.0-rc1/sources/include/machinarium/msg_cache.h000066400000000000000000000021661526064202200233710ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_msgcache mm_msgcache_t; struct mm_msgcache { mm_list_t list; uint64_t count; uint64_t count_allocated; uint64_t count_gc; uint64_t size; int gc_size_watermark; int gc_count_watermark; }; void mm_msgcache_init(mm_msgcache_t *); void mm_msgcache_free(mm_msgcache_t *); void mm_msgcache_stat(mm_msgcache_t *, uint64_t *, uint64_t *, uint64_t *, uint64_t *); mm_msg_t *mm_msgcache_pop(mm_msgcache_t *); void mm_msgcache_push(mm_msgcache_t *, mm_msg_t *); static inline void mm_msgcache_set_gc_watermark(mm_msgcache_t *cache, int size_wm) { cache->gc_size_watermark = size_wm; } static inline void mm_msgcache_set_gc_count_watermark(mm_msgcache_t *cache, int count_wm) { cache->gc_count_watermark = count_wm; } static inline void mm_msg_ref(mm_msg_t *msg) { msg->refs++; } static inline void mm_msg_unref(mm_msgcache_t *cache, mm_msg_t *msg) { if (msg->refs > 0) { msg->refs--; return; } mm_msgcache_push(cache, msg); } odyssey-1.6.0-rc1/sources/include/machinarium/mutex.h000066400000000000000000000011371526064202200226170ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct { atomic_uint_fast64_t state; void *owner_machine; uint64_t owner_coro_id; mm_wait_list_t wl; } mm_mutex_t; void mm_mutex_init(mm_mutex_t *mutex); mm_mutex_t *mm_mutex_create(void); void mm_mutex_free(mm_mutex_t *mutex); void mm_mutex_destroy(mm_mutex_t *mutex); /* returns 1 if mutex is locked, 0 otherwise */ int mm_mutex_lock(mm_mutex_t *mutex, uint32_t timeout_ms); int mm_mutex_lock2(mm_mutex_t *mutex); void mm_mutex_unlock(mm_mutex_t *mutex); odyssey-1.6.0-rc1/sources/include/machinarium/poll.h000066400000000000000000000012221526064202200224160ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include typedef struct mm_pollif mm_pollif_t; typedef struct mm_poll mm_poll_t; struct mm_pollif { char *name; mm_poll_t *(*create)(void); void (*free)(mm_poll_t *); int (*shutdown)(mm_poll_t *); int (*step)(mm_poll_t *, int); int (*add)(mm_poll_t *poll, mm_fd_t *fd, mm_fd_callback_t on_read, void *on_read_arg, mm_fd_callback_t on_write, void *on_write_arg, mm_fd_callback_t on_err, void *on_err_arg, mm_fd_callback_t on_close, void *on_close_arg); int (*del)(mm_poll_t *, mm_fd_t *); }; struct mm_poll { mm_pollif_t *iface; }; odyssey-1.6.0-rc1/sources/include/machinarium/ring_buffer.h000066400000000000000000000015671526064202200237540ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_ring_buffer { size_t capacity; size_t rpos; size_t wpos; size_t size; uint8_t data[]; } mm_ring_buffer_t; mm_ring_buffer_t *mm_ring_buffer_create(size_t capacity); void mm_ring_buffer_free(mm_ring_buffer_t *rbuf); size_t mm_ring_buffer_read(mm_ring_buffer_t *rbuf, void *out, size_t count); size_t mm_ring_buffer_drain(mm_ring_buffer_t *rbuf, size_t count); size_t mm_ring_buffer_write(mm_ring_buffer_t *rbuf, const void *data, size_t count); size_t mm_ring_buffer_size(const mm_ring_buffer_t *rbuf); size_t mm_ring_buffer_capacity(const mm_ring_buffer_t *rbuf); size_t mm_ring_buffer_available(const mm_ring_buffer_t *rbuf); int mm_ring_buffer_is_full(const mm_ring_buffer_t *rbuf); void mm_ring_buffer_clear(mm_ring_buffer_t *rbuf); odyssey-1.6.0-rc1/sources/include/machinarium/scheduler.h000066400000000000000000000025041526064202200234320ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include typedef struct mm_scheduler mm_scheduler_t; struct mm_scheduler { mm_coroutine_t *current; mm_coroutine_t main; int count_ready; int count_active; mm_list_t list_ready; mm_list_t list_active; uint64_t id_seq; }; static inline mm_coroutine_t *mm_scheduler_current(mm_scheduler_t *scheduler) { return scheduler->current; } static inline int mm_scheduler_online(mm_scheduler_t *scheduler) { return scheduler->count_active + scheduler->count_ready; } int mm_scheduler_init(mm_scheduler_t *); void mm_scheduler_free(mm_scheduler_t *); void mm_scheduler_run(mm_scheduler_t *); void mm_scheduler_new(mm_scheduler_t *, mm_coroutine_t *, mm_function_t, void *); mm_coroutine_t *mm_scheduler_find(mm_scheduler_t *, uint64_t); void mm_scheduler_set(mm_scheduler_t *, mm_coroutine_t *, mm_coroutinestate_t); void mm_scheduler_call(mm_scheduler_t *, mm_coroutine_t *); void mm_scheduler_yield(mm_scheduler_t *); void mm_scheduler_join(mm_coroutine_t *, mm_coroutine_t *); static inline void mm_scheduler_wakeup(mm_scheduler_t *scheduler, mm_coroutine_t *coroutine) { mm_scheduler_set(scheduler, coroutine, MM_CREADY); } odyssey-1.6.0-rc1/sources/include/machinarium/sem.h000066400000000000000000000013761526064202200222460ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine * * yet another semaphore implementation for * machinaroum coroutines */ #include #include typedef struct { mm_wait_list_t notifier; atomic_uint_fast64_t value; uint64_t initial_value; } mm_sem_t; void mm_sem_init(mm_sem_t *sem, uint64_t value); void mm_sem_destroy(mm_sem_t *sem); int mm_sem_wait(mm_sem_t *sem); int mm_sem_timedwait(mm_sem_t *sem, uint32_t timeout_ms); void mm_sem_post(mm_sem_t *sem); static inline uint64_t mm_sem_in_use(mm_sem_t *sem) { uint64_t cur = atomic_load(&sem->value); /* cur can theoretically exceed initial_value during concurrent post */ return cur <= sem->initial_value ? sem->initial_value - cur : 0; } odyssey-1.6.0-rc1/sources/include/machinarium/signal_mgr.h000066400000000000000000000014021526064202200235720ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include typedef struct mm_signalrd mm_signalrd_t; typedef struct mm_signalmgr mm_signalmgr_t; struct mm_signalrd { mm_call_t call; int signal; mm_list_t link; }; struct mm_signalmgr { mm_fd_t fd; #if !defined(__linux__) int write_fd; #endif mm_list_t readers; }; /* do not use from several threads/coroutines simultaneously */ int mm_signalmgr_init(mm_signalmgr_t *, mm_loop_t *); void mm_signalmgr_free(mm_signalmgr_t *, mm_loop_t *); int mm_signalmgr_set(mm_signalmgr_t *, sigset_t *, sigset_t *); int mm_signalmgr_wait(mm_signalmgr_t *, uint32_t); odyssey-1.6.0-rc1/sources/include/machinarium/sleep_lock.h000066400000000000000000000012711526064202200235740ustar00rootroot00000000000000#pragma once #include #include /* * machinarium. * * cooperative multitasking engine. */ typedef atomic_uint mm_sleeplock_t; #if defined(__x86_64__) || defined(__i386) || defined(_X86_) #define MM_SLEEPLOCK_BACKOFF __asm__("pause") #else #define MM_SLEEPLOCK_BACKOFF #endif static inline void mm_sleeplock_init(mm_sleeplock_t *lock) { atomic_init(lock, 0); } static inline void mm_sleeplock_lock(mm_sleeplock_t *lock) { unsigned int spin_count = 0U; while (atomic_exchange(lock, 1) != 0) { MM_SLEEPLOCK_BACKOFF; if (++spin_count > 30U) { usleep(1); } } } static inline void mm_sleeplock_unlock(mm_sleeplock_t *lock) { atomic_exchange(lock, 0); } odyssey-1.6.0-rc1/sources/include/machinarium/socket.h000066400000000000000000000023351526064202200227460ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include int mm_socket(int, int, int); int mm_socket_eventfd(unsigned int, int *); int mm_socket_set_nonblock(int, int); int mm_socket_set_nodelay(int, int); int mm_socket_set_keepalive(int fd, int enable, int delay, int interval, int keep_count, int usr_timeout); int mm_socket_advice_keepalive_usr_timeout(int delay, int interval, int keep_count); int mm_socket_set_nosigpipe(int, int); int mm_socket_set_reuseaddr(int, int); int mm_socket_set_reuseport(int, int); int mm_socket_set_nolinger(int fd); int mm_socket_set_ipv6only(int, int); int mm_socket_error(int); int mm_socket_connect(int, struct sockaddr *); int mm_socket_bind(int, struct sockaddr *); int mm_socket_listen(int, int); int mm_socket_accept(int, struct sockaddr *, socklen_t *); int mm_socket_write(int, const void *, int); int mm_socket_writev(int, const struct iovec *, int); int mm_socket_read(int, void *, int); int mm_socket_getsockname(int, struct sockaddr *, socklen_t *); int mm_socket_getpeername(int, struct sockaddr *, socklen_t *); int mm_socket_getaddrinfo(char *, char *, struct addrinfo *, struct addrinfo **); int mm_socket_read_pending(int fd); odyssey-1.6.0-rc1/sources/include/machinarium/spinlock.h000066400000000000000000000020201526064202200232670ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #if defined(__APPLE__) #include typedef os_unfair_lock mm_spinlock_t; static inline void mm_spinlock_init(mm_spinlock_t *lock) { *lock = OS_UNFAIR_LOCK_INIT; } static inline void mm_spinlock_lock(mm_spinlock_t *lock) { os_unfair_lock_lock(lock); } static inline void mm_spinlock_unlock(mm_spinlock_t *lock) { os_unfair_lock_unlock(lock); } static inline void mm_spinlock_destroy(mm_spinlock_t *lock) { (void)lock; } #else /* Linux and other POSIX with pthread spinlock support */ #include typedef pthread_spinlock_t mm_spinlock_t; static inline void mm_spinlock_init(mm_spinlock_t *lock) { pthread_spin_init(lock, PTHREAD_PROCESS_PRIVATE); } static inline void mm_spinlock_lock(mm_spinlock_t *lock) { pthread_spin_lock(lock); } static inline void mm_spinlock_unlock(mm_spinlock_t *lock) { pthread_spin_unlock(lock); } static inline void mm_spinlock_destroy(mm_spinlock_t *lock) { pthread_spin_destroy(lock); } #endif odyssey-1.6.0-rc1/sources/include/machinarium/task.h000066400000000000000000000006401526064202200224150ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include typedef struct mm_task mm_task_t; typedef struct mm_taskmgr_worker { int64_t id; mm_list_t dns_cache; uint64_t dns_ttl_ms; } mm_taskmgr_worker_t; typedef void (*mm_task_function_t)(mm_taskmgr_worker_t *w, void *); struct mm_task { mm_task_function_t function; void *arg; mm_event_t on_complete; }; odyssey-1.6.0-rc1/sources/include/machinarium/task_mgr.h000066400000000000000000000011331526064202200232600ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include typedef struct mm_taskmgr mm_taskmgr_t; struct mm_taskmgr { int workers_count; mm_taskmgr_worker_t *workers; mm_channel_t channel; mm_event_t event; }; void mm_taskmgr_init(mm_taskmgr_t *); int mm_taskmgr_start(mm_taskmgr_t *, int, uint64_t dns_ttl_ms); void mm_taskmgr_stop(mm_taskmgr_t *); int mm_taskmgr_new(mm_taskmgr_t *, mm_task_function_t, void *, uint32_t); odyssey-1.6.0-rc1/sources/include/machinarium/thread.h000066400000000000000000000006761526064202200227330ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include typedef struct mm_thread mm_thread_t; typedef void *(*mm_thread_function_t)(void *); struct mm_thread { pthread_t id; mm_thread_function_t function; void *arg; }; int mm_thread_create(mm_thread_t *, size_t, mm_thread_function_t, void *); int mm_thread_join(mm_thread_t *); int mm_thread_set_name(char *); int mm_thread_disable_cancel(void); odyssey-1.6.0-rc1/sources/include/machinarium/timer.h000066400000000000000000000013571526064202200226010ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_timer mm_timer_t; typedef void (*mm_timer_callback_t)(mm_timer_t *); struct mm_timer { mm_heap_node_t heap; int active; uint64_t timeout; uint32_t interval; int seq; mm_timer_callback_t callback; void *arg; void *clock; }; static inline void mm_timer_init(mm_timer_t *timer, mm_timer_callback_t cb, void *arg, uint32_t interval) { timer->active = 0; timer->interval = interval; timer->timeout = 0; timer->seq = 0; timer->callback = cb; timer->arg = arg; timer->clock = NULL; timer->heap.parent = NULL; timer->heap.left = NULL; timer->heap.right = NULL; } odyssey-1.6.0-rc1/sources/include/machinarium/tls.h000066400000000000000000000015731526064202200222630ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include void mm_tls_engine_init(void); void mm_tls_engine_free(void); int mm_tls_is_active(mm_io_t *io); void mm_tls_init(mm_io_t *); void mm_tls_free(mm_io_t *); void mm_tls_error_reset(mm_io_t *); int mm_tls_handshake(mm_io_t *, uint32_t); int mm_tls_write(mm_io_t *, const char *, int); int mm_tls_writev(mm_io_t *, const struct iovec *, int); int mm_tls_read_pending(mm_io_t *); int mm_tls_read(mm_io_t *, char *, int); int mm_tls_verify_common_name(mm_io_t *, char *); SSL_CTX *mm_tls_get_context(mm_io_t *io, int is_client); void mm_tls_ctx_free(struct mm_tls_ctx *); int mm_tls_get_cert_hash(mm_io_t *io, unsigned char (*cert_hash)[MM_CERT_HASH_LEN], unsigned int *len); odyssey-1.6.0-rc1/sources/include/machinarium/util.h000066400000000000000000000007461526064202200224370ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include static inline int mm_vsnprintf(char *buf, int size, char *fmt, va_list args) { int rc; rc = vsnprintf(buf, size, fmt, args); if (rc >= size) { rc = (size - 1); } return rc; } static inline int mm_snprintf(char *buf, int size, char *fmt, ...) { va_list args; va_start(args, fmt); int rc; rc = mm_vsnprintf(buf, size, fmt, args); va_end(args); return rc; } odyssey-1.6.0-rc1/sources/include/machinarium/wait_flag.h000066400000000000000000000007251526064202200234140ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_wait_flag { atomic_uint_fast64_t value; mm_wait_list_t *waiters; atomic_uint link_count; } mm_wait_flag_t; mm_wait_flag_t *mm_wait_flag_create(void); void mm_wait_flag_destroy(mm_wait_flag_t *flag); void mm_wait_flag_set(mm_wait_flag_t *flag); int mm_wait_flag_wait(mm_wait_flag_t *flag, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/machinarium/wait_group.h000066400000000000000000000011131526064202200236270ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_wait_group { atomic_uint_fast64_t counter; mm_wait_list_t *waiters; atomic_uint link_count; } mm_wait_group_t; mm_wait_group_t *mm_wait_group_create(void); void mm_wait_group_destroy(mm_wait_group_t *group); void mm_wait_group_add(mm_wait_group_t *group); uint64_t mm_wait_group_count(mm_wait_group_t *group); void mm_wait_group_done(mm_wait_group_t *group); int mm_wait_group_wait(mm_wait_group_t *group, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/machinarium/wait_list.h000066400000000000000000000054661526064202200234650ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * A wait list is a structure that is similar to a futex (see futex(2) and futex(7) for details). * It allows a coroutine to wait until a specific condition is met. * Wait lists can be shared among different workers and are suitable for implementing other thread synchronization primitives. * * If compare-and-wait functionality is not needed, you can pass NULL when creating a wait list and simply use the wait(...) method. * However, this may result in lost wake-ups, so do it only if acceptable. * * Local progress is guaranteed (no coroutine starvation) but a FIFO ordering is not. * Spurious wake-ups are possible. */ #include #include #include #include #define MM_SLEEPY_NO_CORO_ID (~0ULL) typedef enum { EXCLUSIVE, SHARED } mm_sleepy_type_t; typedef struct mm_sleepy { union { mm_event_t event; mm_event_t *shared_event; }; mm_sleepy_type_t type; mm_list_t link; /* some additional info about sleepy, to return it from notify() */ void *private; /* we can store coroutine id and we will, in case of some debugging */ uint64_t coro_id; int released; } mm_sleepy_t; typedef struct { mm_sleeplock_t lock; mm_list_t sleepies; uint64_t sleepy_count; /* This field is analogous to a futex word. See futex(2) and futex(7) for details. */ atomic_uint_fast64_t *word; } mm_wait_list_t; /* * The `word` argument in create(...) is analogous to a futex word. * Pass NULL if compare_wait functionality isn't needed. */ void mm_wait_list_init(mm_wait_list_t *wait_list, atomic_uint_fast64_t *word); mm_wait_list_t *mm_wait_list_create(atomic_uint_fast64_t *word); /* * If destroy() is called before all notify() and wait() calls are completed, the behaviour is undefined. * Additional synchronization (e.g., using a wait group) may be required to ensure that. */ void mm_wait_list_destroy(mm_wait_list_t *wait_list); void mm_wait_list_free(mm_wait_list_t *wait_list); int mm_wait_list_wait(mm_wait_list_t *wait_list, void *private, uint32_t timeout_ms); int mm_wait_list_compare_wait(mm_wait_list_t *wait_list, void *private, uint64_t value, uint32_t timeout_ms); /* returns private if someones woke, NULL otherwise */ void *mm_wait_list_notify(mm_wait_list_t *wait_list); int mm_wait_list_notify_all(mm_wait_list_t *wait_list); /* calls cb before signalling the sleepy */ typedef void (*mm_wl_private_cb_t)(void *private, void *arg); void *mm_wait_list_notify_cb(mm_wait_list_t *wait_list, mm_wl_private_cb_t cb, void *arg); /* * Wait for any of provided wait_lists to trigger. * On success returns 0 * On timeout or error returns -1 */ int mm_wait_list_waitv(mm_wait_list_t **wait_lists, size_t count, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/machinarium/zpq_stream.h000066400000000000000000000021041526064202200236350ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #define MM_ZPQ_IO_ERROR (-1) #define MM_ZPQ_DECOMPRESS_ERROR (-2) #define MM_ZPQ_MAX_ALGORITHMS (8) #define MM_ZPQ_NO_COMPRESSION 'n' struct mm_zpq_stream; typedef struct mm_zpq_stream mm_zpq_stream_t; typedef ssize_t (*mm_zpq_tx_func)(void *arg, void const *data, size_t size); typedef ssize_t (*mm_zpq_rx_func)(void *arg, void *data, size_t size); mm_zpq_stream_t *zpq_create(int impl, mm_zpq_tx_func tx_func, mm_zpq_rx_func rx_func, void *arg, char *rx_data, size_t rx_data_size); ssize_t mm_zpq_read(mm_zpq_stream_t *zs, void *buf, size_t size); ssize_t mm_zpq_write(mm_zpq_stream_t *zs, void const *buf, size_t size, size_t *processed); char const *mm_zpq_error(mm_zpq_stream_t *zs); size_t mm_zpq_buffered_tx(mm_zpq_stream_t *zs); size_t mm_zpq_buffered_rx(mm_zpq_stream_t *zs); _Bool mm_zpq_deferred_rx(mm_zpq_stream_t *zs); void mm_zpq_free(mm_zpq_stream_t *zs); void mm_zpq_get_supported_algorithms(char *algorithms); int mm_zpq_get_algorithm_impl(char name); odyssey-1.6.0-rc1/sources/include/mb/000077500000000000000000000000001526064202200174035ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/mb/pg_wchar.h000066400000000000000000000714171526064202200213600ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pg_wchar.h * multibyte-character support * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/mb/pg_wchar.h * * NOTES * This is used both by the backend and by frontends, but should not be * included by libpq client programs. In particular, a libpq client * should not assume that the encoding IDs used by the version of libpq * it's linked to match up with the IDs declared here. * To help prevent mistakes, relevant functions that are exported by * libpq have a physically different name when being referenced * statically. * *------------------------------------------------------------------------- */ #ifndef PG_WCHAR_H #define PG_WCHAR_H /* * The pg_wchar type */ typedef unsigned int pg_wchar; /* * Maximum byte length of multibyte characters in any backend encoding */ #define MAX_MULTIBYTE_CHAR_LEN 4 /* * various definitions for EUC */ #define SS2 0x8e /* single shift 2 (JIS0201) */ #define SS3 0x8f /* single shift 3 (JIS0212) */ /* * SJIS validation macros */ #define ISSJISHEAD(c) \ (((c) >= 0x81 && (c) <= 0x9f) || ((c) >= 0xe0 && (c) <= 0xfc)) #define ISSJISTAIL(c) \ (((c) >= 0x40 && (c) <= 0x7e) || ((c) >= 0x80 && (c) <= 0xfc)) /*---------------------------------------------------- * MULE Internal Encoding (MIC) * * This encoding follows the design used within XEmacs; it is meant to * subsume many externally-defined character sets. Each character includes * identification of the character set it belongs to, so the encoding is * general but somewhat bulky. * * Currently PostgreSQL supports 5 types of MULE character sets: * * 1) 1-byte ASCII characters. Each byte is below 0x80. * * 2) "Official" single byte charsets such as ISO-8859-1 (Latin1). * Each MULE character consists of 2 bytes: LC1 + C1, where LC1 is * an identifier for the charset (in the range 0x81 to 0x8d) and C1 * is the character code (in the range 0xa0 to 0xff). * * 3) "Private" single byte charsets such as SISHENG. Each MULE * character consists of 3 bytes: LCPRV1 + LC12 + C1, where LCPRV1 * is a private-charset flag, LC12 is an identifier for the charset, * and C1 is the character code (in the range 0xa0 to 0xff). * LCPRV1 is either 0x9a (if LC12 is in the range 0xa0 to 0xdf) * or 0x9b (if LC12 is in the range 0xe0 to 0xef). * * 4) "Official" multibyte charsets such as JIS X0208. Each MULE * character consists of 3 bytes: LC2 + C1 + C2, where LC2 is * an identifier for the charset (in the range 0x90 to 0x99) and C1 * and C2 form the character code (each in the range 0xa0 to 0xff). * * 5) "Private" multibyte charsets such as CNS 11643-1992 Plane 3. * Each MULE character consists of 4 bytes: LCPRV2 + LC22 + C1 + C2, * where LCPRV2 is a private-charset flag, LC22 is an identifier for * the charset, and C1 and C2 form the character code (each in the range * 0xa0 to 0xff). LCPRV2 is either 0x9c (if LC22 is in the range 0xf0 * to 0xf4) or 0x9d (if LC22 is in the range 0xf5 to 0xfe). * * "Official" encodings are those that have been assigned code numbers by * the XEmacs project; "private" encodings have Postgres-specific charset * identifiers. * * See the "XEmacs Internals Manual", available at http://www.xemacs.org, * for more details. Note that for historical reasons, Postgres' * private-charset flag values do not match what XEmacs says they should be, * so this isn't really exactly MULE (not that private charsets would be * interoperable anyway). * * Note that XEmacs's implementation is different from what emacs does. * We follow emacs's implementation, rather than XEmacs's. *---------------------------------------------------- */ /* * Charset identifiers (also called "leading bytes" in the MULE documentation) */ /* * Charset IDs for official single byte encodings (0x81-0x8e) */ #define LC_ISO8859_1 0x81 /* ISO8859 Latin 1 */ #define LC_ISO8859_2 0x82 /* ISO8859 Latin 2 */ #define LC_ISO8859_3 0x83 /* ISO8859 Latin 3 */ #define LC_ISO8859_4 0x84 /* ISO8859 Latin 4 */ #define LC_TIS620 0x85 /* Thai (not supported yet) */ #define LC_ISO8859_7 0x86 /* Greek (not supported yet) */ #define LC_ISO8859_6 0x87 /* Arabic (not supported yet) */ #define LC_ISO8859_8 0x88 /* Hebrew (not supported yet) */ #define LC_JISX0201K 0x89 /* Japanese 1 byte kana */ #define LC_JISX0201R 0x8a /* Japanese 1 byte Roman */ /* Note that 0x8b seems to be unused as of Emacs 20.7. * However, there might be a chance that 0x8b could be used * in later versions of Emacs. */ #define LC_KOI8_R 0x8b /* Cyrillic KOI8-R */ #define LC_ISO8859_5 0x8c /* ISO8859 Cyrillic */ #define LC_ISO8859_9 0x8d /* ISO8859 Latin 5 (not supported yet) */ #define LC_ISO8859_15 0x8e /* ISO8859 Latin 15 (not supported yet) */ /* #define CONTROL_1 0x8f control characters (unused) */ /* Is a leading byte for "official" single byte encodings? */ #define IS_LC1(c) ((unsigned char)(c) >= 0x81 && (unsigned char)(c) <= 0x8d) /* * Charset IDs for official multibyte encodings (0x90-0x99) * 0x9a-0x9d are free. 0x9e and 0x9f are reserved. */ #define LC_JISX0208_1978 0x90 /* Japanese Kanji, old JIS (not supported) */ #define LC_GB2312_80 0x91 /* Chinese */ #define LC_JISX0208 0x92 /* Japanese Kanji (JIS X 0208) */ #define LC_KS5601 0x93 /* Korean */ #define LC_JISX0212 0x94 /* Japanese Kanji (JIS X 0212) */ #define LC_CNS11643_1 0x95 /* CNS 11643-1992 Plane 1 */ #define LC_CNS11643_2 0x96 /* CNS 11643-1992 Plane 2 */ #define LC_JISX0213_1 \ 0x97 /* Japanese Kanji (JIS X 0213 Plane 1) * (not supported) */ #define LC_BIG5_1 \ 0x98 /* Plane 1 Chinese traditional (not * supported) */ #define LC_BIG5_2 \ 0x99 /* Plane 1 Chinese traditional (not * supported) */ /* Is a leading byte for "official" multibyte encodings? */ #define IS_LC2(c) ((unsigned char)(c) >= 0x90 && (unsigned char)(c) <= 0x99) /* * Postgres-specific prefix bytes for "private" single byte encodings * (According to the MULE docs, we should be using 0x9e for this) */ #define LCPRV1_A 0x9a #define LCPRV1_B 0x9b #define IS_LCPRV1(c) \ ((unsigned char)(c) == LCPRV1_A || (unsigned char)(c) == LCPRV1_B) #define IS_LCPRV1_A_RANGE(c) \ ((unsigned char)(c) >= 0xa0 && (unsigned char)(c) <= 0xdf) #define IS_LCPRV1_B_RANGE(c) \ ((unsigned char)(c) >= 0xe0 && (unsigned char)(c) <= 0xef) /* * Postgres-specific prefix bytes for "private" multibyte encodings * (According to the MULE docs, we should be using 0x9f for this) */ #define LCPRV2_A 0x9c #define LCPRV2_B 0x9d #define IS_LCPRV2(c) \ ((unsigned char)(c) == LCPRV2_A || (unsigned char)(c) == LCPRV2_B) #define IS_LCPRV2_A_RANGE(c) \ ((unsigned char)(c) >= 0xf0 && (unsigned char)(c) <= 0xf4) #define IS_LCPRV2_B_RANGE(c) \ ((unsigned char)(c) >= 0xf5 && (unsigned char)(c) <= 0xfe) /* * Charset IDs for private single byte encodings (0xa0-0xef) */ #define LC_SISHENG \ 0xa0 /* Chinese SiSheng characters for * PinYin/ZhuYin (not supported) */ #define LC_IPA \ 0xa1 /* IPA (International Phonetic * Association) (not supported) */ #define LC_VISCII_LOWER \ 0xa2 /* Vietnamese VISCII1.1 lower-case (not * supported) */ #define LC_VISCII_UPPER \ 0xa3 /* Vietnamese VISCII1.1 upper-case (not * supported) */ #define LC_ARABIC_DIGIT 0xa4 /* Arabic digit (not supported) */ #define LC_ARABIC_1_COLUMN 0xa5 /* Arabic 1-column (not supported) */ #define LC_ASCII_RIGHT_TO_LEFT \ 0xa6 /* ASCII (left half of ISO8859-1) with * right-to-left direction (not * supported) */ #define LC_LAO \ 0xa7 /* Lao characters (ISO10646 0E80..0EDF) * (not supported) */ #define LC_ARABIC_2_COLUMN 0xa8 /* Arabic 1-column (not supported) */ /* * Charset IDs for private multibyte encodings (0xf0-0xff) */ #define LC_INDIAN_1_COLUMN \ 0xf0 /* Indian charset for 1-column width * glyphs (not supported) */ #define LC_TIBETAN_1_COLUMN \ 0xf1 /* Tibetan 1-column width glyphs (not * supported) */ #define LC_UNICODE_SUBSET_2 \ 0xf2 /* Unicode characters of the range * U+2500..U+33FF. (not supported) */ #define LC_UNICODE_SUBSET_3 \ 0xf3 /* Unicode characters of the range * U+E000..U+FFFF. (not supported) */ #define LC_UNICODE_SUBSET \ 0xf4 /* Unicode characters of the range * U+0100..U+24FF. (not supported) */ #define LC_ETHIOPIC 0xf5 /* Ethiopic characters (not supported) */ #define LC_CNS11643_3 0xf6 /* CNS 11643-1992 Plane 3 */ #define LC_CNS11643_4 0xf7 /* CNS 11643-1992 Plane 4 */ #define LC_CNS11643_5 0xf8 /* CNS 11643-1992 Plane 5 */ #define LC_CNS11643_6 0xf9 /* CNS 11643-1992 Plane 6 */ #define LC_CNS11643_7 0xfa /* CNS 11643-1992 Plane 7 */ #define LC_INDIAN_2_COLUMN \ 0xfb /* Indian charset for 2-column width * glyphs (not supported) */ #define LC_TIBETAN 0xfc /* Tibetan (not supported) */ /* #define FREE 0xfd free (unused) */ /* #define FREE 0xfe free (unused) */ /* #define FREE 0xff free (unused) */ /*---------------------------------------------------- * end of MULE stuff *---------------------------------------------------- */ /* * PostgreSQL encoding identifiers * * WARNING: If you add some encoding don't forget to update * the pg_enc2name_tbl[] array (in src/common/encnames.c), * the pg_enc2gettext_tbl[] array (in src/common/encnames.c) and * the pg_wchar_table[] array (in src/common/wchar.c) and to check * PG_ENCODING_BE_LAST macro. * * PG_SQL_ASCII is default encoding and must be = 0. * * XXX We must avoid renumbering any backend encoding until libpq's major * version number is increased beyond 5; it turns out that the backend * encoding IDs are effectively part of libpq's ABI as far as 8.2 initdb and * psql are concerned. */ typedef enum pg_enc { PG_SQL_ASCII = 0, /* SQL/ASCII */ PG_EUC_JP, /* EUC for Japanese */ PG_EUC_CN, /* EUC for Chinese */ PG_EUC_KR, /* EUC for Korean */ PG_EUC_TW, /* EUC for Taiwan */ PG_EUC_JIS_2004, /* EUC-JIS-2004 */ PG_UTF8, /* Unicode UTF8 */ PG_MULE_INTERNAL, /* Mule internal code */ PG_LATIN1, /* ISO-8859-1 Latin 1 */ PG_LATIN2, /* ISO-8859-2 Latin 2 */ PG_LATIN3, /* ISO-8859-3 Latin 3 */ PG_LATIN4, /* ISO-8859-4 Latin 4 */ PG_LATIN5, /* ISO-8859-9 Latin 5 */ PG_LATIN6, /* ISO-8859-10 Latin6 */ PG_LATIN7, /* ISO-8859-13 Latin7 */ PG_LATIN8, /* ISO-8859-14 Latin8 */ PG_LATIN9, /* ISO-8859-15 Latin9 */ PG_LATIN10, /* ISO-8859-16 Latin10 */ PG_WIN1256, /* windows-1256 */ PG_WIN1258, /* Windows-1258 */ PG_WIN866, /* (MS-DOS CP866) */ PG_WIN874, /* windows-874 */ PG_KOI8R, /* KOI8-R */ PG_WIN1251, /* windows-1251 */ PG_WIN1252, /* windows-1252 */ PG_ISO_8859_5, /* ISO-8859-5 */ PG_ISO_8859_6, /* ISO-8859-6 */ PG_ISO_8859_7, /* ISO-8859-7 */ PG_ISO_8859_8, /* ISO-8859-8 */ PG_WIN1250, /* windows-1250 */ PG_WIN1253, /* windows-1253 */ PG_WIN1254, /* windows-1254 */ PG_WIN1255, /* windows-1255 */ PG_WIN1257, /* windows-1257 */ PG_KOI8U, /* KOI8-U */ /* PG_ENCODING_BE_LAST points to the above entry */ /* followings are for client encoding only */ PG_SJIS, /* Shift JIS (Windows-932) */ PG_BIG5, /* Big5 (Windows-950) */ PG_GBK, /* GBK (Windows-936) */ PG_UHC, /* UHC (Windows-949) */ PG_GB18030, /* GB18030 */ PG_JOHAB, /* EUC for Korean JOHAB */ PG_SHIFT_JIS_2004, /* Shift-JIS-2004 */ _PG_LAST_ENCODING_ /* mark only */ } pg_enc; #define PG_ENCODING_BE_LAST PG_KOI8U /* * Please use these tests before access to pg_enc2name_tbl[] * or to other places... */ #define PG_VALID_BE_ENCODING(_enc) \ ((_enc) >= 0 && (_enc) <= PG_ENCODING_BE_LAST) #define PG_ENCODING_IS_CLIENT_ONLY(_enc) \ ((_enc) > PG_ENCODING_BE_LAST && (_enc) < _PG_LAST_ENCODING_) #define PG_VALID_ENCODING(_enc) ((_enc) >= 0 && (_enc) < _PG_LAST_ENCODING_) /* On FE are possible all encodings */ #define PG_VALID_FE_ENCODING(_enc) PG_VALID_ENCODING(_enc) /* * When converting strings between different encodings, we assume that space * for converted result is 4-to-1 growth in the worst case. The rate for * currently supported encoding pairs are within 3 (SJIS JIS X0201 half width * kana -> UTF8 is the worst case). So "4" should be enough for the moment. * * Note that this is not the same as the maximum character width in any * particular encoding. */ #define MAX_CONVERSION_GROWTH 4 /* * Maximum byte length of a string that's required in any encoding to convert * at least one character to any other encoding. In other words, if you feed * MAX_CONVERSION_INPUT_LENGTH bytes to any encoding conversion function, it * is guaranteed to be able to convert something without needing more input * (assuming the input is valid). * * Currently, the maximum case is the conversion UTF8 -> SJIS JIS X0201 half * width kana, where a pair of UTF-8 characters is converted into a single * SHIFT_JIS_2004 character (the reverse of the worst case for * MAX_CONVERSION_GROWTH). It needs 6 bytes of input. In theory, a * user-defined conversion function might have more complicated cases, although * for the reverse mapping you would probably also need to bump up * MAX_CONVERSION_GROWTH. But there is no need to be stingy here, so make it * generous. */ #define MAX_CONVERSION_INPUT_LENGTH 16 /* * Maximum byte length of the string equivalent to any one Unicode code point, * in any backend encoding. The current value assumes that a 4-byte UTF-8 * character might expand by MAX_CONVERSION_GROWTH, which is a huge * overestimate. But in current usage we don't allocate large multiples of * this, so there's little point in being stingy. */ #define MAX_UNICODE_EQUIVALENT_STRING 16 /* * Table for mapping an encoding number to official encoding name and * possibly other subsidiary data. Be careful to check encoding number * before accessing a table entry! * * if (PG_VALID_ENCODING(encoding)) * pg_enc2name_tbl[ encoding ]; */ typedef struct pg_enc2name { const char *name; pg_enc encoding; #ifdef WIN32 unsigned codepage; /* codepage for WIN32 */ #endif } pg_enc2name; extern PGDLLIMPORT const pg_enc2name pg_enc2name_tbl[]; /* * Encoding names for gettext */ extern PGDLLIMPORT const char *pg_enc2gettext_tbl[]; /* * pg_wchar stuff */ typedef int (*mb2wchar_with_len_converter)(const unsigned char *from, pg_wchar *to, int len); typedef int (*wchar2mb_with_len_converter)(const pg_wchar *from, unsigned char *to, int len); typedef int (*mblen_converter)(const unsigned char *mbstr); typedef int (*mbdisplaylen_converter)(const unsigned char *mbstr); typedef bool (*mbcharacter_incrementer)(unsigned char *mbstr, int len); typedef int (*mbchar_verifier)(const unsigned char *mbstr, int len); typedef int (*mbstr_verifier)(const unsigned char *mbstr, int len); typedef struct { mb2wchar_with_len_converter mb2wchar_with_len; /* convert a multibyte * string to a wchar */ wchar2mb_with_len_converter wchar2mb_with_len; /* convert a wchar string * to a multibyte */ mblen_converter mblen; /* get byte length of a char */ mbdisplaylen_converter dsplen; /* get display width of a char */ mbchar_verifier mbverifychar; /* verify multibyte character */ mbstr_verifier mbverifystr; /* verify multibyte string */ int maxmblen; /* max bytes for a char in this encoding */ } pg_wchar_tbl; extern PGDLLIMPORT const pg_wchar_tbl pg_wchar_table[]; /* * Data structures for conversions between UTF-8 and other encodings * (UtfToLocal() and LocalToUtf()). In these data structures, characters of * either encoding are represented by uint32 words; hence we can only support * characters up to 4 bytes long. For example, the byte sequence 0xC2 0x89 * would be represented by 0x0000C289, and 0xE8 0xA2 0xB4 by 0x00E8A2B4. * * There are three possible ways a character can be mapped: * * 1. Using a radix tree, from source to destination code. * 2. Using a sorted array of source -> destination code pairs. This * method is used for "combining" characters. There are so few of * them that building a radix tree would be wasteful. * 3. Using a conversion function. */ /* * Radix tree for character conversion. * * Logically, this is actually four different radix trees, for 1-byte, * 2-byte, 3-byte and 4-byte inputs. The 1-byte tree is a simple lookup * table from source to target code. The 2-byte tree consists of two levels: * one lookup table for the first byte, where the value in the lookup table * points to a lookup table for the second byte. And so on. * * Physically, all the trees are stored in one big array, in 'chars16' or * 'chars32', depending on the maximum value that needs to be represented. For * each level in each tree, we also store lower and upper bound of allowed * values - values outside those bounds are considered invalid, and are left * out of the tables. * * In the intermediate levels of the trees, the values stored are offsets * into the chars[16|32] array. * * In the beginning of the chars[16|32] array, there is always a number of * zeros, so that you safely follow an index from an intermediate table * without explicitly checking for a zero. Following a zero any number of * times will always bring you to the dummy, all-zeros table in the * beginning. This helps to shave some cycles when looking up values. */ typedef struct { /* * Array containing all the values. Only one of chars16 or chars32 is * used, depending on how wide the values we need to represent are. */ const uint16 *chars16; const uint32 *chars32; /* Radix tree for 1-byte inputs */ uint32 b1root; /* offset of table in the chars[16|32] array */ uint8 b1_lower; /* min allowed value for a single byte input */ uint8 b1_upper; /* max allowed value for a single byte input */ /* Radix tree for 2-byte inputs */ uint32 b2root; /* offset of 1st byte's table */ uint8 b2_1_lower; /* min/max allowed value for 1st input byte */ uint8 b2_1_upper; uint8 b2_2_lower; /* min/max allowed value for 2nd input byte */ uint8 b2_2_upper; /* Radix tree for 3-byte inputs */ uint32 b3root; /* offset of 1st byte's table */ uint8 b3_1_lower; /* min/max allowed value for 1st input byte */ uint8 b3_1_upper; uint8 b3_2_lower; /* min/max allowed value for 2nd input byte */ uint8 b3_2_upper; uint8 b3_3_lower; /* min/max allowed value for 3rd input byte */ uint8 b3_3_upper; /* Radix tree for 4-byte inputs */ uint32 b4root; /* offset of 1st byte's table */ uint8 b4_1_lower; /* min/max allowed value for 1st input byte */ uint8 b4_1_upper; uint8 b4_2_lower; /* min/max allowed value for 2nd input byte */ uint8 b4_2_upper; uint8 b4_3_lower; /* min/max allowed value for 3rd input byte */ uint8 b4_3_upper; uint8 b4_4_lower; /* min/max allowed value for 4th input byte */ uint8 b4_4_upper; } pg_mb_radix_tree; /* * UTF-8 to local code conversion map (for combined characters) */ typedef struct { uint32 utf1; /* UTF-8 code 1 */ uint32 utf2; /* UTF-8 code 2 */ uint32 code; /* local code */ } pg_utf_to_local_combined; /* * local code to UTF-8 conversion map (for combined characters) */ typedef struct { uint32 code; /* local code */ uint32 utf1; /* UTF-8 code 1 */ uint32 utf2; /* UTF-8 code 2 */ } pg_local_to_utf_combined; /* * callback function for algorithmic encoding conversions (in either direction) * * if function returns zero, it does not know how to convert the code */ typedef uint32 (*utf_local_conversion_func)(uint32 code); /* * Support macro for encoding conversion functions to validate their * arguments. (This could be made more compact if we included fmgr.h * here, but we don't want to do that because this header file is also * used by frontends.) */ #define CHECK_ENCODING_CONVERSION_ARGS(srcencoding, destencoding) \ check_encoding_conversion_args(PG_GETARG_INT32(0), PG_GETARG_INT32(1), \ PG_GETARG_INT32(4), (srcencoding), \ (destencoding)) /* * Some handy functions for Unicode-specific tests. */ static inline bool is_valid_unicode_codepoint(char32_t c) { return (c > 0 && c <= 0x10FFFF); } static inline bool is_utf16_surrogate_first(char32_t c) { return (c >= 0xD800 && c <= 0xDBFF); } static inline bool is_utf16_surrogate_second(char32_t c) { return (c >= 0xDC00 && c <= 0xDFFF); } static inline char32_t surrogate_pair_to_codepoint(char16_t first, char16_t second) { return ((first & 0x3FF) << 10) + 0x10000 + (second & 0x3FF); } /* * Convert a UTF-8 character to a Unicode code point. * This is a one-character version of pg_utf2wchar_with_len. * * No error checks here, c must point to a long-enough string. */ static inline char32_t utf8_to_unicode(const unsigned char *c) { if ((*c & 0x80) == 0) { return (char32_t)c[0]; } else if ((*c & 0xe0) == 0xc0) { return (char32_t)(((c[0] & 0x1f) << 6) | (c[1] & 0x3f)); } else if ((*c & 0xf0) == 0xe0) { return (char32_t)(((c[0] & 0x0f) << 12) | ((c[1] & 0x3f) << 6) | (c[2] & 0x3f)); } else if ((*c & 0xf8) == 0xf0) { return (char32_t)(((c[0] & 0x07) << 18) | ((c[1] & 0x3f) << 12) | ((c[2] & 0x3f) << 6) | (c[3] & 0x3f)); } else { /* that is an invalid code on purpose */ return 0xffffffff; } } /* * Map a Unicode code point to UTF-8. utf8string must have at least * unicode_utf8len(c) bytes available. */ static inline unsigned char *unicode_to_utf8(char32_t c, unsigned char *utf8string) { if (c <= 0x7F) { utf8string[0] = c; } else if (c <= 0x7FF) { utf8string[0] = 0xC0 | ((c >> 6) & 0x1F); utf8string[1] = 0x80 | (c & 0x3F); } else if (c <= 0xFFFF) { utf8string[0] = 0xE0 | ((c >> 12) & 0x0F); utf8string[1] = 0x80 | ((c >> 6) & 0x3F); utf8string[2] = 0x80 | (c & 0x3F); } else { utf8string[0] = 0xF0 | ((c >> 18) & 0x07); utf8string[1] = 0x80 | ((c >> 12) & 0x3F); utf8string[2] = 0x80 | ((c >> 6) & 0x3F); utf8string[3] = 0x80 | (c & 0x3F); } return utf8string; } /* * Number of bytes needed to represent the given char in UTF8. */ static inline int unicode_utf8len(char32_t c) { if (c <= 0x7F) { return 1; } else if (c <= 0x7FF) { return 2; } else if (c <= 0xFFFF) { return 3; } else { return 4; } } /* * The functions in this list are exported by libpq, and we need to be sure * that we know which calls are satisfied by libpq and which are satisfied * by static linkage to libpgcommon. (This is because we might be using a * libpq.so that's of a different major version and has encoding IDs that * differ from the current version's.) The nominal function names are what * are actually used in and exported by libpq, while the names exported by * libpgcommon.a and libpgcommon_srv.a end in "_private". */ #if defined(USE_PRIVATE_ENCODING_FUNCS) || !defined(FRONTEND) #define pg_char_to_encoding pg_char_to_encoding_private #define pg_encoding_to_char pg_encoding_to_char_private #define pg_valid_server_encoding pg_valid_server_encoding_private #define pg_valid_server_encoding_id pg_valid_server_encoding_id_private #define pg_utf_mblen pg_utf_mblen_private #endif /* * These functions are considered part of libpq's exported API and * are also declared in libpq-fe.h. */ extern int pg_char_to_encoding(const char *name); extern const char *pg_encoding_to_char(int encoding); extern int pg_valid_server_encoding_id(int encoding); /* * These functions are available to frontend code that links with libpgcommon * (in addition to the ones just above). The constant tables declared * earlier in this file are also available from libpgcommon. */ extern void pg_encoding_set_invalid(int encoding, char *dst); extern int pg_encoding_mblen(int encoding, const char *mbstr); extern int pg_encoding_mblen_or_incomplete(int encoding, const char *mbstr, size_t remaining); extern int pg_encoding_mblen_bounded(int encoding, const char *mbstr); extern int pg_encoding_dsplen(int encoding, const char *mbstr); extern int pg_encoding_verifymbchar(int encoding, const char *mbstr, int len); extern int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len); extern int pg_encoding_max_length(int encoding); extern int pg_valid_client_encoding(const char *name); extern int pg_valid_server_encoding(const char *name); extern bool is_encoding_supported_by_icu(int encoding); extern const char *get_encoding_name_for_icu(int encoding); extern bool pg_utf8_islegal(const unsigned char *source, int length); extern int pg_utf_mblen(const unsigned char *s); extern int pg_mule_mblen(const unsigned char *s); /* * The remaining functions are backend-only. */ extern int pg_mb2wchar(const char *from, pg_wchar *to); extern int pg_mb2wchar_with_len(const char *from, pg_wchar *to, int len); extern int pg_encoding_mb2wchar_with_len(int encoding, const char *from, pg_wchar *to, int len); extern int pg_wchar2mb(const pg_wchar *from, char *to); extern int pg_wchar2mb_with_len(const pg_wchar *from, char *to, int len); extern int pg_encoding_wchar2mb_with_len(int encoding, const pg_wchar *from, char *to, int len); extern int pg_char_and_wchar_strcmp(const char *s1, const pg_wchar *s2); extern int pg_wchar_strncmp(const pg_wchar *s1, const pg_wchar *s2, size_t n); extern int pg_char_and_wchar_strncmp(const char *s1, const pg_wchar *s2, size_t n); extern size_t pg_wchar_strlen(const pg_wchar *str); extern int pg_mblen(const char *mbstr); extern int pg_dsplen(const char *mbstr); extern int pg_mbstrlen(const char *mbstr); extern int pg_mbstrlen_with_len(const char *mbstr, int limit); extern int pg_mbcliplen(const char *mbstr, int len, int limit); extern int pg_encoding_mbcliplen(int encoding, const char *mbstr, int len, int limit); extern int pg_mbcharcliplen(const char *mbstr, int len, int limit); extern int pg_database_encoding_max_length(void); extern mbcharacter_incrementer pg_database_encoding_character_incrementer(void); extern int PrepareClientEncoding(int encoding); extern int SetClientEncoding(int encoding); extern void InitializeClientEncoding(void); extern int pg_get_client_encoding(void); extern const char *pg_get_client_encoding_name(void); extern void SetDatabaseEncoding(int encoding); extern int GetDatabaseEncoding(void); extern const char *GetDatabaseEncodingName(void); extern void SetMessageEncoding(int encoding); extern int GetMessageEncoding(void); #ifdef ENABLE_NLS extern int pg_bind_textdomain_codeset(const char *domainname); #endif extern unsigned char *pg_do_encoding_conversion(unsigned char *src, int len, int src_encoding, int dest_encoding); /*extern int pg_do_encoding_conversion_buf(Oid proc, int src_encoding, int dest_encoding, unsigned char *src, int srclen, unsigned char *dest, int destlen, bool noError);*/ extern char *pg_client_to_server(const char *s, int len); extern char *pg_server_to_client(const char *s, int len); extern char *pg_any_to_server(const char *s, int len, int encoding); extern char *pg_server_to_any(const char *s, int len, int encoding); extern void pg_unicode_to_server(char32_t c, unsigned char *s); extern bool pg_unicode_to_server_noerror(char32_t c, unsigned char *s); extern unsigned short BIG5toCNS(unsigned short big5, unsigned char *lc); extern unsigned short CNStoBIG5(unsigned short cns, unsigned char lc); extern int UtfToLocal(const unsigned char *utf, int len, unsigned char *iso, const pg_mb_radix_tree *map, const pg_utf_to_local_combined *cmap, int cmapsize, utf_local_conversion_func conv_func, int encoding, bool noError); extern int LocalToUtf(const unsigned char *iso, int len, unsigned char *utf, const pg_mb_radix_tree *map, const pg_local_to_utf_combined *cmap, int cmapsize, utf_local_conversion_func conv_func, int encoding, bool noError); extern bool pg_verifymbstr(const char *mbstr, int len, bool noError); extern bool pg_verify_mbstr(int encoding, const char *mbstr, int len, bool noError); extern int pg_verify_mbstr_len(int encoding, const char *mbstr, int len, bool noError); extern void check_encoding_conversion_args(int src_encoding, int dest_encoding, int len, int expected_src_encoding, int expected_dest_encoding); pg_noreturn extern void report_invalid_encoding(int encoding, const char *mbstr, int len); pg_noreturn extern void report_untranslatable_char(int src_encoding, int dest_encoding, const char *mbstr, int len); extern int local2local(const unsigned char *l, unsigned char *p, int len, int src_encoding, int dest_encoding, const unsigned char *tab, bool noError); extern int latin2mic(const unsigned char *l, unsigned char *p, int len, int lc, int encoding, bool noError); extern int mic2latin(const unsigned char *mic, unsigned char *p, int len, int lc, int encoding, bool noError); extern int latin2mic_with_table(const unsigned char *l, unsigned char *p, int len, int lc, int encoding, const unsigned char *tab, bool noError); extern int mic2latin_with_table(const unsigned char *mic, unsigned char *p, int len, int lc, int encoding, const unsigned char *tab, bool noError); #ifdef WIN32 extern WCHAR *pgwin32_message_to_UTF16(const char *str, int len, int *utf16len); #endif #endif /* PG_WCHAR_H */ odyssey-1.6.0-rc1/sources/include/mdb_iamproxy.h000066400000000000000000000003401526064202200216450ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int mdb_iamproxy_authenticate_user(char *username, char *token, od_instance_t *instance, od_client_t *client); odyssey-1.6.0-rc1/sources/include/misc.h000066400000000000000000000007601526064202200201140ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ extern int pg_strcasecmp(const char *s1, const char *s2); extern bool parse_bool(const char *value, bool *result); extern bool parse_bool_with_len(const char *value, size_t len, bool *result); #define HIGHBIT (0x80) #define IS_HIGHBIT_SET(ch) ((unsigned char)(ch) & HIGHBIT) #define od_min(a, b) ((a) < (b) ? (a) : (b)) #define od_max(a, b) ((a) > (b) ? (a) : (b)) #define ROUNDUPDIV(a, b) (((a) + (b) - 1) / (b)) odyssey-1.6.0-rc1/sources/include/module.h000066400000000000000000000040241526064202200204430ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #define OD_LOAD_MODULE "od_module" #define od_load_module(handle) (od_module_t *)od_dlsym(handle, OD_LOAD_MODULE) #define OD_MODULE_CB_OK_RETCODE 0 #define OD_MODULE_CB_FAIL_RETCODE -1 /* init */ typedef int (*module_init_cb_t)(od_logger_t *logger); /* auth */ typedef int (*client_auth_attempt_cb_t)(od_client_t *c); typedef int (*client_auth_complete_cb_t)(od_client_t *c, int rc); typedef int (*client_disconnect_cb_t)(od_client_t *c, od_frontend_status_t s); /* reload */ typedef od_retcode_t (*od_config_reload_cb_t)(od_list_t *added, od_list_t *deleted); /* nonexcluzive auth cb */ typedef od_retcode_t (*od_auth_cleartext_cb_t)(od_client_t *cl, kiwi_password_t *tok); /* unload */ typedef int (*module_unload_cb_t)(void); #define MAX_MODULE_PATH_LEN 2048 struct od_module { void *handle; char path[MAX_MODULE_PATH_LEN]; /* Handlers */ /*---------------------------------*/ module_init_cb_t module_init_cb; client_auth_attempt_cb_t auth_attempt_cb; client_auth_complete_cb_t auth_complete_cb; client_disconnect_cb_t disconnect_cb; od_config_reload_cb_t od_config_reload_cb; od_auth_cleartext_cb_t od_auth_cleartext_cb; module_unload_cb_t unload_cb; /*---------------------------------*/ od_list_t link; }; typedef struct od_module od_module_t; extern void od_modules_init(od_module_t *module); extern int od_target_module_add(od_logger_t *logger, od_module_t *modules, char *target_module_path); extern od_module_t *od_modules_find(od_module_t *modules, char *target_module_path); extern int od_target_module_unload(od_logger_t *logger, od_module_t *modules, char *target_module); extern int od_modules_unload(od_logger_t *logger, od_module_t *modules); /* function tio perform "fast" unload all modules, */ /* here we do not wait for module-defined unload callback */ extern int od_modules_unload_fast(od_module_t *modules); odyssey-1.6.0-rc1/sources/include/msg.h000066400000000000000000000004001526064202200177360ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef enum { OD_MSG_STAT, OD_MSG_CLIENT_NEW, OD_MSG_LOG, OD_MSG_SHUTDOWN, OD_MSG_SIGNAL_RECEIVED, OD_MSG_GRAC_SHUTDOWN_FINISHED, OD_MSG_RELOAD, OD_MSG_GC, } od_msg_t; odyssey-1.6.0-rc1/sources/include/multi_pool.h000066400000000000000000000046601526064202200213470ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* * address -> pool 'map' */ #include #include #include #include #include #include #include typedef void (*od_server_pool_free_fn_t)(od_server_pool_t *); struct od_multi_pool_key { char *dbname; char *username; od_address_t address; }; struct od_multi_pool_element { od_multi_pool_key_t key; od_server_pool_t pool; mm_wait_list_t wait_bus; od_list_t link; }; typedef int (*od_multi_pool_element_cb_t)(od_multi_pool_element_t *, void **); struct od_multi_pool { od_list_t pools; od_server_pool_free_fn_t pool_free_fn; mm_mutex_t lock; /* should increase every time servers in the route's pool are changed */ atomic_uint_fast64_t version; }; static inline void od_multi_pool_lock(od_multi_pool_t *mpool) { mm_mutex_lock2(&mpool->lock); } static inline void od_multi_pool_unlock(od_multi_pool_t *mpool) { mm_mutex_unlock(&mpool->lock); } static inline uint64_t od_multi_pool_version(od_multi_pool_t *mpool) { return atomic_load(&mpool->version); } void od_multi_pool_signal_locked(od_multi_pool_t *mpool, od_multi_pool_element_t *el, od_server_t *server); int od_multi_pool_wait(od_multi_pool_element_t *el, od_client_t *client, uint64_t version, uint32_t timeout_ms); od_multi_pool_t *od_multi_pool_create(od_server_pool_free_fn_t pool_free_fn); void od_multi_pool_destroy(od_multi_pool_t *mpool); od_multi_pool_element_t * od_multi_pool_get_or_create_locked(od_multi_pool_t *mpool, const od_multi_pool_key_t *key); /* return 1 if key fits, 0 otherwise */ typedef int (*od_multi_pool_key_filter_t)(void *, const od_multi_pool_key_t *); od_server_t * od_multi_pool_foreach_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg, od_server_state_t state, od_server_pool_cb_t callback, void **argv); int od_multi_pool_count_active_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg); int od_multi_pool_count_idle_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg); int od_multi_pool_total_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg); od_server_t *od_multi_pool_peek_any_locked(od_multi_pool_t *mpool, od_server_state_t state); odyssey-1.6.0-rc1/sources/include/od_assert.h000066400000000000000000000021321526064202200211370ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* * od_assert — assert that logs through logger before aborting * * unlike the standard assert(), the failure message is written to the * configured log destination (file / syslog) so it is not lost when * stderr is unavailable */ #include extern void od_assert_fail(const char *expr, const char *file, int line); #ifdef NDEBUG #define od_assert(expr) ((void)1) #else #define od_assert(expr) \ do { \ if (od_unlikely(!(expr))) { \ od_assert_fail(#expr, __FILE__, __LINE__); \ } \ } while (0) #endif #define od_release_assert(expr) \ do { \ if (od_unlikely(!(expr))) { \ od_assert_fail(#expr, __FILE__, __LINE__); \ } \ } while (0) OD_NORETURN void od_abort(void); odyssey-1.6.0-rc1/sources/include/od_c.h000066400000000000000000000063061526064202200200670ustar00rootroot00000000000000#pragma once /* * fundamental and common C definitions * should be included in every .c file by odyssey.h */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define od_likely(EXPR) __builtin_expect(!!(EXPR), 1) #define od_unlikely(EXPR) __builtin_expect(!!(EXPR), 0) #if defined(__linux__) #define od_read_mostly __attribute__((__section__(".data.read_mostly"))) #else #define od_read_mostly #endif #define od_container_of(N, T, F) ((T *)((char *)(N) - __builtin_offsetof(T, F))) #ifndef lengthof #define lengthof(array) (sizeof(array) / sizeof((array)[0])) #endif #define OK_RESPONSE 0 #define NOT_OK_RESPONSE -1 static const uint64_t interval_usec = 1000000ull; typedef int od_retcode_t; #define INVALID_COROUTINE_ID -1 #if defined(__GNUC__) || defined(__clang__) #define OD_NORETURN __attribute__((noreturn)) #define OD_UNREACHABLE() __builtin_unreachable() #else #define OD_NORETURN #define OD_UNREACHABLE() abort() #endif #define FLEXIBLE_ARRAY_MEMBER /* empty */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L #define OD_THREAD_LOCAL thread_local #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define OD_THREAD_LOCAL _Thread_local #elif defined(_MSC_VER) #define OD_THREAD_LOCAL __declspec(thread) #else #define OD_THREAD_LOCAL __thread #endif #if defined(__linux__) #include #elif defined(__APPLE__) #include #define htobe16(x) OSSwapHostToBigInt16(x) #define htole16(x) OSSwapHostToLittleInt16(x) #define be16toh(x) OSSwapBigToHostInt16(x) #define le16toh(x) OSSwapLittleToHostInt16(x) #define htobe32(x) OSSwapHostToBigInt32(x) #define htole32(x) OSSwapHostToLittleInt32(x) #define be32toh(x) OSSwapBigToHostInt32(x) #define le32toh(x) OSSwapLittleToHostInt32(x) #define htobe64(x) OSSwapHostToBigInt64(x) #define htole64(x) OSSwapHostToLittleInt64(x) #define be64toh(x) OSSwapBigToHostInt64(x) #define le64toh(x) OSSwapLittleToHostInt64(x) #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) #include #elif defined(__OpenBSD__) #include #include /* OpenBSD historically lacks the *toh names in some versions */ #ifndef be16toh #define be16toh(x) betoh16(x) #define le16toh(x) letoh16(x) #define be32toh(x) betoh32(x) #define le32toh(x) letoh32(x) #define be64toh(x) betoh64(x) #define le64toh(x) letoh64(x) #endif #else #error "machinarium: unsupported platform, no endian.h replacement available" #endif static inline void od_explicit_bzero(void *buf, size_t len) { memset(buf, 0, len); /* prevent the compiler from optimizing away the memset above */ __asm__ __volatile__("" : : "r"(buf) : "memory"); } #if !defined(explicit_bzero) #define explicit_bzero od_explicit_bzero #endif /* included there, because it uses some macroses from this file */ #include odyssey-1.6.0-rc1/sources/include/od_dlsym.h000066400000000000000000000004451526064202200207730ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #define od_dlopen(path) dlopen((char *)path, RTLD_NOW | RTLD_GLOBAL) #define od_dlsym(handle, symbol) dlsym(handle, symbol) #define od_dlerror dlerror #define od_dlclose(handle) dlclose(handle) odyssey-1.6.0-rc1/sources/include/od_error.h000066400000000000000000000025001526064202200207660ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef struct od_error od_error_t; #define OD_ERROR_MAX_LEN 256 struct od_error { char file[OD_ERROR_MAX_LEN]; int file_len; char function[128]; int function_len; char error[OD_ERROR_MAX_LEN]; int error_len; int line; }; static inline void od_error_init(od_error_t *error) { error->file[0] = 0; error->file_len = 0; error->function[0] = 0; error->function_len = 0; error->error[0] = 0; error->error_len = 0; error->line = 0; } static inline void od_error_setv(od_error_t *error, const char *file, const char *function, int line, char *fmt, va_list args) { error->file_len = od_snprintf(error->file, sizeof(error->file), "%s", file); error->function_len = od_snprintf( error->function, sizeof(error->function), "%s", function); error->line = line; int len; len = od_vsnprintf(error->error, sizeof(error->error), fmt, args); error->error_len = len; } static inline int od_error_set(od_error_t *error, const char *file, const char *function, int line, char *fmt, ...) { va_list args; va_start(args, fmt); od_error_setv(error, file, function, line, fmt, args); va_end(args); return -1; } #define od_errorf(error, fmt, ...) \ od_error_set(error, __FILE__, __func__, __LINE__, fmt, __VA_ARGS__) odyssey-1.6.0-rc1/sources/include/od_ldap.h000066400000000000000000000010531526064202200205570ustar00rootroot00000000000000#pragma once #ifdef LDAP_FOUND #include #include #include #include #include #include #include #include /* For functions ldap_unbind, ldap_search_s, ldap_simple_bind_s */ #define LDAP_DEPRECATED 1 #include int od_auth_ldap(od_client_t *cl, kiwi_password_t *tok); int od_auth_ldap_resolve_storage_credentials(od_client_t *cl, od_rule_t *rule); int od_ldap_workers_init(size_t count); void od_ldap_workers_destroy(void); #endif /* LDAP_FOUND */ odyssey-1.6.0-rc1/sources/include/od_memory.h000066400000000000000000000006501526064202200211510ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include void *od_malloc(size_t size); void od_free(void *ptr); void *od_calloc(size_t nmemb, size_t size); void *od_realloc(void *ptr, size_t size); char *od_strdup(const char *s); char *od_strndup(const char *s, size_t n); /* can be set to NULL to disable usage of alloc */ void od_set_thread_linear_alloc(od_linear_alloc_t *la); odyssey-1.6.0-rc1/sources/include/odyssey.h000066400000000000000000000003111526064202200206500ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * general definitions for all odyssey source files * should be included first */ #include #include odyssey-1.6.0-rc1/sources/include/option.h000066400000000000000000000057271526064202200205010ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include extern void od_usage(od_instance_t *instance, char *path); extern int od_config_testing(od_instance_t *instance); extern void od_print_version(void); extern void od_print_help(const char *program); extern void fill_supported_features_string(char *out, size_t max); extern int od_format_version_string(char *buf, size_t size); typedef struct { od_instance_t *instance; int silent; int verbose; int console; int log_stdout; int test; } od_arguments_t; typedef enum { OD_OPT_CONSOLE = 10001, /* >= than any utf symbol like -q -l etc */ OD_OPT_SILENT, OD_OPT_VERBOSE, OD_OPT_LOG_STDOUT, OD_OPT_TEST, OD_OPT_VERSION, OD_OPT_USAGE, } od_cli_options; static const char *short_opts = "Vh?"; static struct option long_opts[] = { { "verbose", no_argument, NULL, OD_OPT_VERBOSE }, { "silent", no_argument, NULL, OD_OPT_SILENT }, { "console", no_argument, NULL, OD_OPT_CONSOLE }, { "log_to_stdout", no_argument, NULL, OD_OPT_LOG_STDOUT }, { "test", no_argument, NULL, OD_OPT_TEST }, { "version", no_argument, NULL, OD_OPT_VERSION }, { "help", no_argument, NULL, 'h' }, { "usage", no_argument, NULL, OD_OPT_USAGE }, { 0, 0, 0, 0 } }; static inline void od_parse_args(int argc, char **argv, od_arguments_t *args) { od_instance_t *instance = args->instance; for (;;) { int opt_idx = 0; int c = getopt_long(argc, argv, short_opts, long_opts, &opt_idx); if (c == -1) { break; } switch (c) { case OD_OPT_SILENT: args->silent = 1; break; case OD_OPT_VERBOSE: args->verbose = 1; break; case 'h': od_print_help(argv[0]); exit(0); case OD_OPT_VERSION: case 'V': od_print_version(); exit(0); case OD_OPT_USAGE: printf("Usage: %s [-?V] [--console] [--log_to_stdout] " "[--silent] [--test] [--verbose] [--help] " "[--usage] [--version] /path/to/odyssey.conf\n", argv[0]); exit(0); case OD_OPT_CONSOLE: args->console = 1; break; case OD_OPT_LOG_STDOUT: args->log_stdout = 1; break; case OD_OPT_TEST: args->test = 1; break; case '?': if (optopt == 0 && optind > 0 && argv[optind - 1] != NULL && strcmp(argv[optind - 1], "-?") == 0) { od_print_help(argv[0]); exit(0); } fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); exit(1); default: od_usage(instance, instance->exec_path); exit(1); } } if (argc - optind > 1) { /* Too many arguments. */ od_usage(instance, instance->exec_path); exit(1); } if (argc - optind < 1) { /* Not enough arguments. */ od_usage(instance, instance->exec_path); exit(1); } instance->config_file = od_strdup(argv[optind]); if (args->test == 1) { exit(od_config_testing(instance)); } } extern od_retcode_t od_apply_validate_cli_args(od_logger_t *logger, od_config_t *conf, od_arguments_t *args, od_rules_t *rules); odyssey-1.6.0-rc1/sources/include/pam.h000066400000000000000000000010301526064202200177250ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include struct od_pam_auth_data { int msg_style; char *value; od_list_t link; }; typedef struct od_pam_auth_data od_pam_auth_data_t; int od_pam_auth(char *od_pam_service, char *usrname, od_pam_auth_data_t *auth_data, mm_io_t *io); void od_pam_convert_passwd(od_pam_auth_data_t *d, char *passwd); od_pam_auth_data_t *od_pam_auth_data_create(void); void od_pam_auth_data_free(od_pam_auth_data_t *d); odyssey-1.6.0-rc1/sources/include/pg_compat.h000066400000000000000000000074621526064202200211400ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /*------------------------------------------------------------------------- * * c.h * Fundamental C definitions. This is included by every .c file in * PostgreSQL (via either postgres.h or postgres_fe.h, as appropriate). * * Note that the definitions here are not intended to be exposed to clients * of the frontend interface libraries --- so we don't worry much about * polluting the namespace with lots of stuff... * * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/c.h * *------------------------------------------------------------------------- */ /* various things to use postgresql source files at odyssey */ #define int8 int8_t #define uint8 uint8_t #define uint16 uint16_t #define uint32 uint32_t #define uint64 uint64_t #define int64 int64_t typedef uint16_t char16_t; typedef uint32_t char32_t; typedef size_t Size; #define Assert(p) od_assert(p) #define AssertMacro(p) ((void)od_assert(p)) #define lengthof(array) (sizeof(array) / sizeof((array)[0])) #define pg_attribute_noreturn() _NORETURN #define gettext(x) (x) #define _(x) gettext(x) #ifndef pg_nodiscard #define pg_nodiscard __attribute__((warn_unused_result)) #endif /* * pg_noreturn corresponds to the C11 noreturn/_Noreturn function specifier. * We can't use the standard name "noreturn" because some third-party code * uses __attribute__((noreturn)) in headers, which would get confused if * "noreturn" is defined to "_Noreturn", as is done by . * * In a declaration, function specifiers go before the function name. The * common style is to put them before the return type. (The MSVC fallback has * the same requirement. The GCC fallback is more flexible.) */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define pg_noreturn _Noreturn #elif defined(__GNUC__) #define pg_noreturn __attribute__((noreturn)) #elif defined(_MSC_VER) #define pg_noreturn __declspec(noreturn) #else #define pg_noreturn #endif /* only GCC supports the unused attribute */ #ifdef __GNUC__ #define pg_attribute_unused() __attribute__((unused)) #else #define pg_attribute_unused() #endif /* GCC supports format attributes */ #if defined(__GNUC__) #define pg_attribute_format_arg(a) __attribute__((format_arg(a))) #define pg_attribute_printf(f, a) \ __attribute__((format(PG_PRINTF_ATTRIBUTE, f, a))) #else #define pg_attribute_format_arg(a) #define pg_attribute_printf(f, a) #endif #define PG_C_PRINTF_ATTRIBUTE printf #ifndef __cplusplus #define PG_PRINTF_ATTRIBUTE PG_C_PRINTF_ATTRIBUTE #else #define PG_PRINTF_ATTRIBUTE PG_CXX_PRINTF_ATTRIBUTE #endif /* * Append PG_USED_FOR_ASSERTS_ONLY to definitions of variables that are only * used in assert-enabled builds, to avoid compiler warnings about unused * variables in assert-disabled builds. */ #ifdef USE_ASSERT_CHECKING #define PG_USED_FOR_ASSERTS_ONLY #else #define PG_USED_FOR_ASSERTS_ONLY pg_attribute_unused() #endif #define PGDLLIMPORT /* * MemSet * just repplaced with stdlib memset * while original pg MemSet have some optimizations */ #define MemSet(start, val, len) memset((start), (val), (len)) #define INT64CONST(x) INT64_C(x) #define UINT64CONST(x) UINT64_C(x) /* msb for char */ #define HIGHBIT (0x80) #define IS_HIGHBIT_SET(ch) ((unsigned char)(ch) & HIGHBIT) /* * Undefine OPENSSL_API_COMPAT to prevent conflicts with system OpenSSL headers. * PostgreSQL's pg_config.h defines this, but on OpenSSL 1.1.x, the system headers * define it differently (as OPENSSL_MIN_API), causing redefinition errors. * This is only needed for OpenSSL < 3.0. */ #include #if defined(OPENSSL_API_COMPAT) && OPENSSL_VERSION_NUMBER < 0x30000000L #undef OPENSSL_API_COMPAT #endif odyssey-1.6.0-rc1/sources/include/pid.h000066400000000000000000000010551526064202200177330ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include typedef struct od_pid od_pid_t; struct od_pid { pid_t pid; pid_t restart_ppid; atomic_int_fast32_t restart_new_pid; char pid_sz[16]; int pid_len; }; void od_pid_init(od_pid_t *); int od_pid_create(od_pid_t *, char *); int od_pid_unlink(od_pid_t *, char *); void od_pid_restart_new_set(od_pid_t *p, pid_t pid); pid_t od_pid_restart_new_get(od_pid_t *p); #define OD_SIG_LOG_ROTATE SIGUSR1 #define OD_SIG_ONLINE_RESTART SIGUSR2 odyssey-1.6.0-rc1/sources/include/pool.h000066400000000000000000000031161526064202200201300ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef struct od_rule_pool od_rule_pool_t; typedef enum { OD_RULE_POOL_SESSION, OD_RULE_POOL_TRANSACTION, OD_RULE_POOL_STATEMENT, } od_rule_pool_type_t; typedef enum { OD_RULE_POOL_INTERNAL, OD_RULE_POOL_CLIENT_VISIBLE, } od_rule_routing_type_t; typedef enum { OD_POOL_CLIENT_UNDEF, OD_POOL_CLIENT_INTERNAL, OD_POOL_CLIENT_EXTERNAL, } od_pool_client_type_t; struct od_rule_pool { od_rule_pool_type_t pool_type; od_rule_routing_type_t routing; char *pool_type_str; char *routing_type; char *discard_query; int min_size; int size; int timeout; int ttl; int discard; int smart_discard; int cancel; int rollback; int pin_on_listen; int notice_after_waiting_ms; int attach_check; int acquire_fail_fast; uint64_t reset_timeout_ms; /* -------- makes sense only for transaction pooling --------------------------- */ int reserve_prepared_statement; int server_pstmt_cache_size; /* ------------------------------------------------------------------------------ */ /* -------- makes sense only for session pooling ------------------------------- */ uint64_t client_idle_timeout; uint64_t idle_in_transaction_timeout; /* ------------------------------------------------------------------------------ */ }; od_rule_pool_t *od_rule_pool_alloc(void); int od_rule_pool_free(od_rule_pool_t *pool); int od_rule_pool_compare(od_rule_pool_t *a, od_rule_pool_t *b); int od_rule_matches_client(od_rule_pool_t *pool, od_pool_client_type_t t); int od_rule_pool_can_add(const od_rule_pool_t *pool, int size); odyssey-1.6.0-rc1/sources/include/port/000077500000000000000000000000001526064202200177715ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/port/pg_bswap.h000066400000000000000000000071231526064202200217470ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pg_bswap.h * Byte swapping. * * Macros for reversing the byte order of 16, 32 and 64-bit unsigned integers. * For example, 0xAABBCCDD becomes 0xDDCCBBAA. These are just wrappers for * built-in functions provided by the compiler where support exists. * * Note that all of these functions accept unsigned integers as arguments and * return the same. Use caution when using these wrapper macros with signed * integers. * * Copyright (c) 2015-2025, PostgreSQL Global Development Group * * src/include/port/pg_bswap.h * *------------------------------------------------------------------------- */ #ifndef PG_BSWAP_H #define PG_BSWAP_H /* * In all supported versions msvc provides _byteswap_* functions in stdlib.h, * already included by c.h. */ /* implementation of uint16 pg_bswap16(uint16) */ #if defined(HAVE__BUILTIN_BSWAP16) #define pg_bswap16(x) __builtin_bswap16(x) #elif defined(_MSC_VER) #define pg_bswap16(x) _byteswap_ushort(x) #else static inline uint16 pg_bswap16(uint16 x) { return ((x << 8) & 0xff00) | ((x >> 8) & 0x00ff); } #endif /* HAVE__BUILTIN_BSWAP16 */ /* implementation of uint32 pg_bswap32(uint32) */ #if defined(HAVE__BUILTIN_BSWAP32) #define pg_bswap32(x) __builtin_bswap32(x) #elif defined(_MSC_VER) #define pg_bswap32(x) _byteswap_ulong(x) #else static inline uint32 pg_bswap32(uint32 x) { return ((x << 24) & 0xff000000) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | ((x >> 24) & 0x000000ff); } #endif /* HAVE__BUILTIN_BSWAP32 */ /* implementation of uint64 pg_bswap64(uint64) */ #if defined(HAVE__BUILTIN_BSWAP64) #define pg_bswap64(x) __builtin_bswap64(x) #elif defined(_MSC_VER) #define pg_bswap64(x) _byteswap_uint64(x) #else static inline uint64 pg_bswap64(uint64 x) { return ((x << 56) & UINT64CONST(0xff00000000000000)) | ((x << 40) & UINT64CONST(0x00ff000000000000)) | ((x << 24) & UINT64CONST(0x0000ff0000000000)) | ((x << 8) & UINT64CONST(0x000000ff00000000)) | ((x >> 8) & UINT64CONST(0x00000000ff000000)) | ((x >> 24) & UINT64CONST(0x0000000000ff0000)) | ((x >> 40) & UINT64CONST(0x000000000000ff00)) | ((x >> 56) & UINT64CONST(0x00000000000000ff)); } #endif /* HAVE__BUILTIN_BSWAP64 */ /* * Portable and fast equivalents for ntohs, ntohl, htons, htonl, * additionally extended to 64 bits. */ #ifdef WORDS_BIGENDIAN #define pg_hton16(x) (x) #define pg_hton32(x) (x) #define pg_hton64(x) (x) #define pg_ntoh16(x) (x) #define pg_ntoh32(x) (x) #define pg_ntoh64(x) (x) #else #define pg_hton16(x) pg_bswap16(x) #define pg_hton32(x) pg_bswap32(x) #define pg_hton64(x) pg_bswap64(x) #define pg_ntoh16(x) pg_bswap16(x) #define pg_ntoh32(x) pg_bswap32(x) #define pg_ntoh64(x) pg_bswap64(x) #endif /* WORDS_BIGENDIAN */ /* * Rearrange the bytes of a Datum from big-endian order into the native byte * order. On big-endian machines, this does nothing at all. * * One possible application of the DatumBigEndianToNative() macro is to make * bitwise comparisons cheaper. A simple 3-way comparison of Datums * transformed by the macro (based on native, unsigned comparisons) will return * the same result as a memcmp() of the corresponding original Datums, but can * be much cheaper. It's generally safe to do this on big-endian systems * without any special transformation occurring first. */ #ifdef WORDS_BIGENDIAN #define DatumBigEndianToNative(x) (x) #else /* !WORDS_BIGENDIAN */ #define DatumBigEndianToNative(x) UInt64GetDatum(pg_bswap64(DatumGetUInt64(x))) #endif /* WORDS_BIGENDIAN */ #endif /* PG_BSWAP_H */ odyssey-1.6.0-rc1/sources/include/port/simd.h000066400000000000000000000345231526064202200211050ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * simd.h * Support for platform-specific vector operations. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/port/simd.h * * NOTES * - VectorN in this file refers to a register where the element operands * are N bits wide. The vector width is platform-specific, so users that care * about that will need to inspect "sizeof(VectorN)". * *------------------------------------------------------------------------- */ #ifndef SIMD_H #define SIMD_H #if (defined(__x86_64__) || defined(_M_AMD64)) /* * SSE2 instructions are part of the spec for the 64-bit x86 ISA. We assume * that compilers targeting this architecture understand SSE2 intrinsics. * * We use emmintrin.h rather than the comprehensive header immintrin.h in * order to exclude extensions beyond SSE2. This is because MSVC, at least, * will allow the use of intrinsics that haven't been enabled at compile * time. */ #include #define USE_SSE2 typedef __m128i Vector8; typedef __m128i Vector32; #elif defined(__aarch64__) && defined(__ARM_NEON) /* * We use the Neon instructions if the compiler provides access to them (as * indicated by __ARM_NEON) and we are on aarch64. While Neon support is * technically optional for aarch64, it appears that all available 64-bit * hardware does have it. Neon exists in some 32-bit hardware too, but we * could not realistically use it there without a run-time check, which seems * not worth the trouble for now. */ #include #define USE_NEON typedef uint8x16_t Vector8; typedef uint32x4_t Vector32; #else /* * If no SIMD instructions are available, we can in some cases emulate vector * operations using bitwise operations on unsigned integers. Note that many * of the functions in this file presently do not have non-SIMD * implementations. In particular, none of the functions involving Vector32 * are implemented without SIMD since it's likely not worthwhile to represent * two 32-bit integers using a uint64. */ #define USE_NO_SIMD typedef uint64 Vector8; #endif /* load/store operations */ static inline void vector8_load(Vector8 *v, const uint8 *s); #ifndef USE_NO_SIMD static inline void vector32_load(Vector32 *v, const uint32 *s); #endif /* assignment operations */ static inline Vector8 vector8_broadcast(const uint8 c); #ifndef USE_NO_SIMD static inline Vector32 vector32_broadcast(const uint32 c); #endif /* element-wise comparisons to a scalar */ static inline bool vector8_has(const Vector8 v, const uint8 c); static inline bool vector8_has_zero(const Vector8 v); static inline bool vector8_has_le(const Vector8 v, const uint8 c); static inline bool vector8_is_highbit_set(const Vector8 v); #ifndef USE_NO_SIMD static inline bool vector32_is_highbit_set(const Vector32 v); static inline uint32 vector8_highbit_mask(const Vector8 v); #endif /* arithmetic operations */ static inline Vector8 vector8_or(const Vector8 v1, const Vector8 v2); #ifndef USE_NO_SIMD static inline Vector32 vector32_or(const Vector32 v1, const Vector32 v2); #endif /* * comparisons between vectors * * Note: These return a vector rather than boolean, which is why we don't * have non-SIMD implementations. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_eq(const Vector8 v1, const Vector8 v2); static inline Vector8 vector8_min(const Vector8 v1, const Vector8 v2); static inline Vector32 vector32_eq(const Vector32 v1, const Vector32 v2); #endif /* * Load a chunk of memory into the given vector. */ static inline void vector8_load(Vector8 *v, const uint8 *s) { #if defined(USE_SSE2) *v = _mm_loadu_si128((const __m128i *)s); #elif defined(USE_NEON) *v = vld1q_u8(s); #else memcpy(v, s, sizeof(Vector8)); #endif } #ifndef USE_NO_SIMD static inline void vector32_load(Vector32 *v, const uint32 *s) { #ifdef USE_SSE2 *v = _mm_loadu_si128((const __m128i *)s); #elif defined(USE_NEON) *v = vld1q_u32(s); #endif } #endif /* ! USE_NO_SIMD */ /* * Store a vector into the given memory address. */ #ifndef USE_NO_SIMD static inline void vector8_store(uint8 *s, Vector8 v) { #ifdef USE_SSE2 _mm_storeu_si128((Vector8 *)s, v); #elif defined(USE_NEON) vst1q_u8(s, v); #endif } #endif /* ! USE_NO_SIMD */ /* * Create a vector with all elements set to the same value. */ static inline Vector8 vector8_broadcast(const uint8 c) { #if defined(USE_SSE2) return _mm_set1_epi8(c); #elif defined(USE_NEON) return vdupq_n_u8(c); #else return ~UINT64CONST(0) / 0xFF * c; #endif } #ifndef USE_NO_SIMD static inline Vector32 vector32_broadcast(const uint32 c) { #ifdef USE_SSE2 return _mm_set1_epi32(c); #elif defined(USE_NEON) return vdupq_n_u32(c); #endif } #endif /* ! USE_NO_SIMD */ /* * Return true if any elements in the vector are equal to the given scalar. */ static inline bool vector8_has(const Vector8 v, const uint8 c) { bool result; #if defined(USE_NO_SIMD) /* any bytes in v equal to c will evaluate to zero via XOR */ result = vector8_has_zero(v ^ vector8_broadcast(c)); #else result = vector8_is_highbit_set(vector8_eq(v, vector8_broadcast(c))); #endif return result; } /* * Convenience function equivalent to vector8_has(v, 0) */ static inline bool vector8_has_zero(const Vector8 v) { #if defined(USE_NO_SIMD) /* * We cannot call vector8_has() here, because that would lead to a * circular definition. */ return vector8_has_le(v, 0); #else return vector8_has(v, 0); #endif } /* * Return true if any elements in the vector are less than or equal to the * given scalar. */ static inline bool vector8_has_le(const Vector8 v, const uint8 c) { bool result = false; #ifdef USE_SSE2 Vector8 umin; Vector8 cmpe; #endif #if defined(USE_NO_SIMD) /* * To find bytes <= c, we can use bitwise operations to find bytes < c+1, * but it only works if c+1 <= 128 and if the highest bit in v is not set. * Adapted from * https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord */ if ((int64)v >= 0 && c < 0x80) { result = (v - vector8_broadcast(c + 1)) & ~v & vector8_broadcast(0x80); } else { /* one byte at a time */ for (Size i = 0; i < sizeof(Vector8); i++) { if (((const uint8 *)&v)[i] <= c) { result = true; break; } } } #elif defined(USE_SSE2) umin = vector8_min(v, vector8_broadcast(c)); cmpe = vector8_eq(umin, v); result = vector8_is_highbit_set(cmpe); #elif defined(USE_NEON) result = vminvq_u8(v) <= c; #endif return result; } /* * Returns true if any elements in the vector are greater than or equal to the * given scalar. */ #ifndef USE_NO_SIMD static inline bool vector8_has_ge(const Vector8 v, const uint8 c) { #ifdef USE_SSE2 Vector8 umax = _mm_max_epu8(v, vector8_broadcast(c)); Vector8 cmpe = vector8_eq(umax, v); return vector8_is_highbit_set(cmpe); #elif defined(USE_NEON) return vmaxvq_u8(v) >= c; #endif } #endif /* ! USE_NO_SIMD */ /* * Return true if the high bit of any element is set */ static inline bool vector8_is_highbit_set(const Vector8 v) { #ifdef USE_SSE2 return _mm_movemask_epi8(v) != 0; #elif defined(USE_NEON) return vmaxvq_u8(v) > 0x7F; #else return v & vector8_broadcast(0x80); #endif } /* * Exactly like vector8_is_highbit_set except for the input type, so it * looks at each byte separately. * * XXX x86 uses the same underlying type for 8-bit, 16-bit, and 32-bit * integer elements, but Arm does not, hence the need for a separate * function. We could instead adopt the behavior of Arm's vmaxvq_u32(), i.e. * check each 32-bit element, but that would require an additional mask * operation on x86. */ #ifndef USE_NO_SIMD static inline bool vector32_is_highbit_set(const Vector32 v) { #if defined(USE_NEON) return vector8_is_highbit_set((Vector8)v); #else return vector8_is_highbit_set(v); #endif } #endif /* ! USE_NO_SIMD */ /* * Return a bitmask formed from the high-bit of each element. */ #ifndef USE_NO_SIMD static inline uint32 vector8_highbit_mask(const Vector8 v) { #ifdef USE_SSE2 return (uint32)_mm_movemask_epi8(v); #elif defined(USE_NEON) /* * Note: It would be faster to use vget_lane_u64 and vshrn_n_u16, but that * returns a uint64, making it inconvenient to combine mask values from * multiple vectors. */ static const uint8 mask[16] = { 1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7, 1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7, }; uint8x16_t masked = vandq_u8(vld1q_u8(mask), (uint8x16_t)vshrq_n_s8((int8x16_t)v, 7)); uint8x16_t maskedhi = vextq_u8(masked, masked, 8); return (uint32)vaddvq_u16((uint16x8_t)vzip1q_u8(masked, maskedhi)); #endif } #endif /* ! USE_NO_SIMD */ /* * Return the bitwise OR of the inputs */ static inline Vector8 vector8_or(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_or_si128(v1, v2); #elif defined(USE_NEON) return vorrq_u8(v1, v2); #else return v1 | v2; #endif } #ifndef USE_NO_SIMD static inline Vector32 vector32_or(const Vector32 v1, const Vector32 v2) { #ifdef USE_SSE2 return _mm_or_si128(v1, v2); #elif defined(USE_NEON) return vorrq_u32(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Return the bitwise AND of the inputs. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_and(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_and_si128(v1, v2); #elif defined(USE_NEON) return vandq_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Return the result of adding the respective elements of the input vectors. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_add(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_add_epi8(v1, v2); #elif defined(USE_NEON) return vaddq_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Return the result of subtracting the respective elements of the input * vectors using signed saturation (i.e., if the operation would yield a value * less than -128, -128 is returned instead). For more information on * saturation arithmetic, see * https://en.wikipedia.org/wiki/Saturation_arithmetic */ #ifndef USE_NO_SIMD static inline Vector8 vector8_issub(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_subs_epi8(v1, v2); #elif defined(USE_NEON) return (Vector8)vqsubq_s8((int8x16_t)v1, (int8x16_t)v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Return a vector with all bits set in each lane where the corresponding * lanes in the inputs are equal. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_eq(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_cmpeq_epi8(v1, v2); #elif defined(USE_NEON) return vceqq_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ #ifndef USE_NO_SIMD static inline Vector32 vector32_eq(const Vector32 v1, const Vector32 v2) { #ifdef USE_SSE2 return _mm_cmpeq_epi32(v1, v2); #elif defined(USE_NEON) return vceqq_u32(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Return a vector with all bits set for each lane of v1 that is greater than * the corresponding lane of v2. NB: The comparison treats the elements as * signed. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_gt(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_cmpgt_epi8(v1, v2); #elif defined(USE_NEON) return vcgtq_s8((int8x16_t)v1, (int8x16_t)v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Given two vectors, return a vector with the minimum element of each. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_min(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_min_epu8(v1, v2); #elif defined(USE_NEON) return vminq_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Interleave elements of low halves (e.g., for SSE2, bits 0-63) of given * vectors. Bytes 0, 2, 4, etc. use v1, and bytes 1, 3, 5, etc. use v2. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_interleave_low(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_unpacklo_epi8(v1, v2); #elif defined(USE_NEON) return vzip1q_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Interleave elements of high halves (e.g., for SSE2, bits 64-127) of given * vectors. Bytes 0, 2, 4, etc. use v1, and bytes 1, 3, 5, etc. use v2. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_interleave_high(const Vector8 v1, const Vector8 v2) { #ifdef USE_SSE2 return _mm_unpackhi_epi8(v1, v2); #elif defined(USE_NEON) return vzip2q_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Pack 16-bit elements in the given vectors into a single vector of 8-bit * elements. The first half of the return vector (e.g., for SSE2, bits 0-63) * uses v1, and the second half (e.g., for SSE2, bits 64-127) uses v2. * * NB: The upper 8-bits of each 16-bit element must be zeros, else this will * produce different results on different architectures. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_pack_16(const Vector8 v1, const Vector8 v2) { Vector8 mask PG_USED_FOR_ASSERTS_ONLY; mask = vector8_interleave_low(vector8_broadcast(0), vector8_broadcast(0xff)); Assert(!vector8_has_ge(vector8_and(v1, mask), 1)); Assert(!vector8_has_ge(vector8_and(v2, mask), 1)); #ifdef USE_SSE2 return _mm_packus_epi16(v1, v2); #elif defined(USE_NEON) return vuzp1q_u8(v1, v2); #endif } #endif /* ! USE_NO_SIMD */ /* * Unsigned shift left of each 32-bit element in the vector by "i" bits. * * XXX AArch64 requires an integer literal, so we have to list all expected * values of "i" from all callers in a switch statement. If you add a new * caller, be sure your expected values of "i" are handled. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_shift_left(const Vector8 v1, int i) { #ifdef USE_SSE2 return _mm_slli_epi32(v1, i); #elif defined(USE_NEON) switch (i) { case 4: return (Vector8)vshlq_n_u32((Vector32)v1, 4); default: Assert(false); return vector8_broadcast(0); } #endif } #endif /* ! USE_NO_SIMD */ /* * Unsigned shift right of each 32-bit element in the vector by "i" bits. * * XXX AArch64 requires an integer literal, so we have to list all expected * values of "i" from all callers in a switch statement. If you add a new * caller, be sure your expected values of "i" are handled. */ #ifndef USE_NO_SIMD static inline Vector8 vector8_shift_right(const Vector8 v1, int i) { #ifdef USE_SSE2 return _mm_srli_epi32(v1, i); #elif defined(USE_NEON) switch (i) { case 4: return (Vector8)vshrq_n_u32((Vector32)v1, 4); case 8: return (Vector8)vshrq_n_u32((Vector32)v1, 8); default: Assert(false); return vector8_broadcast(0); } #endif } #endif /* ! USE_NO_SIMD */ #endif /* SIMD_H */ odyssey-1.6.0-rc1/sources/include/postgres_fe.h000066400000000000000000000016131526064202200214770ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * postgres_fe.h * Primary include file for PostgreSQL client-side .c files * * This should be the first file included by PostgreSQL client libraries and * application programs --- but not by backend modules, which should include * postgres.h. * * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1995, Regents of the University of California * * src/include/postgres_fe.h * *------------------------------------------------------------------------- */ /* IWYU pragma: always_keep */ #ifndef POSTGRES_FE_H #define POSTGRES_FE_H #ifndef FRONTEND #define FRONTEND 1 #endif /* IWYU pragma: begin_exports */ #include "od_c.h" #include "od_memory.h" #include "pg_compat.h" #include "common/fe_memutils.h" /* IWYU pragma: end_exports */ #endif /* POSTGRES_FE_H */ odyssey-1.6.0-rc1/sources/include/prom_metrics.h000066400000000000000000000045011526064202200216610ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef struct od_prom_metrics od_prom_metrics_t; struct od_prom_metrics { prom_collector_registry_t *stat_general_metrics; prom_gauge_t *database_len; prom_gauge_t *user_len; prom_gauge_t *server_pool_active; prom_gauge_t *server_pool_idle; prom_gauge_t *msg_allocated; prom_gauge_t *msg_cache_count; prom_gauge_t *msg_cache_gc_count; prom_gauge_t *msg_cache_size; prom_gauge_t *count_coroutine; prom_gauge_t *count_coroutine_cache; prom_gauge_t *clients_processed; prom_collector_registry_t *stat_route_metrics; prom_gauge_t *client_pool_total; prom_gauge_t *avg_tx_count; prom_gauge_t *avg_tx_time; prom_gauge_t *avg_query_count; prom_gauge_t *avg_query_time; prom_gauge_t *avg_recv_client; prom_gauge_t *avg_recv_server; struct MHD_Daemon *http_server; int port; }; extern int od_prom_metrics_init(od_prom_metrics_t *self); int od_prom_set_port(int port, od_prom_metrics_t *self); /* Activate metrics delivery via http*/ int od_prom_activate_general_metrics(od_prom_metrics_t *self); int od_prom_activate_route_metrics(od_prom_metrics_t *self); extern int od_prom_metrics_write_stat(od_prom_metrics_t *self, u_int64_t msg_allocated, u_int64_t msg_cache_count, u_int64_t msg_cache_gc_count, u_int64_t msg_cache_size, u_int64_t count_coroutine, u_int64_t count_coroutine_cache); extern int od_prom_metrics_write_worker_stat( struct od_prom_metrics *self, int worker_id, u_int64_t msg_allocated, u_int64_t msg_cache_count, u_int64_t msg_cache_gc_count, u_int64_t msg_cache_size, u_int64_t count_coroutine, u_int64_t count_coroutine_cache, u_int64_t clients_processed); extern const char *od_prom_metrics_get_stat(od_prom_metrics_t *self); extern int od_prom_metrics_write_stat_cb( od_prom_metrics_t *self, const char *user, const char *database, u_int64_t database_len, u_int64_t user_len, u_int64_t client_pool_total, u_int64_t server_pool_active, u_int64_t server_pool_idle, u_int64_t avg_tx_count, u_int64_t avg_tx_time, u_int64_t avg_query_count, u_int64_t avg_query_time, u_int64_t avg_recv_client, u_int64_t avg_recv_server); extern const char *od_prom_metrics_get_stat_cb(od_prom_metrics_t *self); extern int od_prom_metrics_destroy(od_prom_metrics_t *self); odyssey-1.6.0-rc1/sources/include/pstmt.h000066400000000000000000000116041526064202200203270ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* * some helpers for working with prepared statements * * for pstmt support implementation there must be next hashmaps created * * for client: * client pstmt name -> *pstmt description * * for server: * server pstmt name -> *pstmt description * * global: * pstmt description -> ref counter * * ex.: * client sends parse "P0" with query "Q" * the description will be "Q" (+ param bytes) * client name will be "P0" * hash will be xxh64("Q" + param bytes) * server name will be "pstmt_#" where # is global counter */ #include #include #include #include #include #define OD_PSTMT_NAME_PREFIX "odyssey_pstmt_" #define OD_MAX_PSTMT_NUM (99999999999999999UL) #define OD_PSTMT_NAME_MAX_LEN \ (sizeof(OD_PSTMT_NAME_PREFIX) + 17 /* length of OD_MAX_PSTMT_NUM */) typedef char od_pstmt_name_t[OD_PSTMT_NAME_MAX_LEN]; /* query and param bytes from Parse message */ typedef struct { void *data; size_t len; } od_pstmt_desc_t; typedef struct { mm_hashmap_t *hm; atomic_uint_fast64_t counter; } od_global_pstmt_map_t; struct od_pstmt { /* own the desc->data copy */ od_pstmt_desc_t desc; od_pstmt_name_t name; /* * query context flags, parsed once from desc.data via * od_sql_minimal_parse at pstmt creation time. * deallocate_name is stored inplace (no heap allocation). */ od_query_ctx_t query_ctx; /* * holded by: * - client pstmts, portals * - server pstmts * - xplan entries * - global pstmts map * * after reach 1, the stmt must be deleted from global map */ atomic_uint_fast64_t refs; od_global_pstmt_map_t *source; }; /* "P_0" -> *od_pstmt_t */ mm_hashmap_t *od_client_pstmt_hashmap_create(void); void od_client_pstmt_hashmap_free(mm_hashmap_t *hm); int od_client_add_pstmt(od_client_t *client, const char *name, od_pstmt_t *pstmt); int od_client_has_pstmt(od_client_t *client, const char *name); int od_client_remove_pstmt(od_client_t *client, const char *name); od_pstmt_t *od_client_get_pstmt(od_client_t *client, const char *name); void od_client_pstmts_clear(od_client_t *client); /* "portal_name" -> *od_pstmt_t (portal tracking, extended protocol) */ mm_hashmap_t *od_client_portal_hashmap_create(void); void od_client_portal_hashmap_free(mm_hashmap_t *hm); int od_client_add_portal(od_client_t *client, const char *portal_name, od_pstmt_t *pstmt); od_pstmt_t *od_client_get_portal(od_client_t *client, const char *portal_name); int od_client_remove_portal(od_client_t *client, const char *portal_name); void od_client_portals_clear(od_client_t *client); /* "odyssey_pstmt_0" -> *od_pstmt_t */ mm_hashmap_t *od_server_pstmt_hashmap_create(void); void od_server_pstmts_free(od_server_t *server); int od_server_has_pstmt(od_server_t *server, const od_pstmt_t *pstmt); int od_server_add_pstmt(od_server_t *server, od_pstmt_t *pstmt); int od_server_remove_pstmt(od_server_t *server, const od_pstmt_t *pstmt); void od_server_pstmts_clear(od_server_t *server); /* accessor for the value of the server pstmt hashmap (used by console) */ const od_pstmt_t *od_server_pstmt_kvp_pstmt(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp); /* SIEVE eviction of reserved statements in excess of cap; returns count or -1 */ int od_server_pstmt_evict_overflow(od_server_t *server, size_t cap, machine_msg_t *stream); /* od_pstmt_desc_t -> od_pstmt_t */ od_global_pstmt_map_t *od_global_pstmts_map_create(size_t nlocks); void od_global_pstmts_map_free(od_global_pstmt_map_t *hm); od_pstmt_t *od_pstmt_create_or_get(od_global_pstmt_map_t *gm, od_pstmt_desc_t desc, od_linear_alloc_t *arena); int od_global_pstmts_has_pstmt(od_global_pstmt_map_t *gm, const od_pstmt_desc_t desc); void od_global_pstmt_try_remove(od_global_pstmt_map_t *gm, od_pstmt_t *pstmt); typedef int (*od_global_pstmt_cb)(const od_pstmt_t *pstmt, void *arg); void od_global_pstmt_foreach(od_global_pstmt_map_t *gm, od_global_pstmt_cb cb, void *arg); /* helpers */ char *od_pstmt_name_from_parse(machine_msg_t *msg); od_pstmt_desc_t od_pstmt_desc_from_parse(machine_msg_t *msg); od_pstmt_desc_t od_pstmt_desc_copy(const od_pstmt_desc_t desc); machine_msg_t *od_pstmt_parse_of(const od_pstmt_t *pstmt); machine_msg_t *od_pstmt_describe_of(const od_pstmt_t *pstmt); /* * should be called only with * - lock on global map held * - or from ref that is already valid */ static inline void od_pstmt_ref(od_pstmt_t *pstmt) { atomic_fetch_add_explicit(&pstmt->refs, 1, memory_order_relaxed); } static inline void od_pstmt_unref(od_pstmt_t *pstmt) { uint64_t v = atomic_fetch_sub_explicit(&pstmt->refs, 1, memory_order_release); od_assert(v > 1); if (v == 2) { /* * some other thread can ref this pstmt in parallel * this fact will be rechecked in remove fn */ od_global_pstmt_try_remove(pstmt->source, pstmt); } } odyssey-1.6.0-rc1/sources/include/query.h000066400000000000000000000010131526064202200203160ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include /* execute query with (optional) single string param */ machine_msg_t *od_query_do(od_server_t *server, char *context, const char *query, char *param, uint32_t timeout_ms); __attribute__((hot)) extern int od_query_format(char *format_pos, char *format_end, kiwi_var_t *user, char *peer, char *output, int output_len); odyssey-1.6.0-rc1/sources/include/rate.h000066400000000000000000000017161526064202200201160ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* * Minimal token-bucket rate limiter, inspired by Go's time/rate package. * No burst support (burst is effectively 1). The limiter refills tokens * at a fixed rate of `limit` events per second. */ #include #include typedef struct od_rate_limiter { /* Per second */ uint64_t limit; int64_t tokens; /* last token update time */ int64_t last; mm_spinlock_t lock; } od_rate_limiter_t; void od_rate_limiter_init(od_rate_limiter_t *lim, uint64_t limit); void od_rate_limiter_destroy(od_rate_limiter_t *lim); /* * WaitN blocks until the limiter permits n events to happen. * Returns 0 on success, -1 on timeout and * if n > 1 and the limiter cannot ever satisfy the request * with the given rate. * * If limit is 0, always returns -1 immediately. */ int od_rate_limiter_waitn(od_rate_limiter_t *lim, uint64_t n, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/readahead.h000066400000000000000000000036741526064202200210660ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef struct od_readahead od_readahead_t; struct od_readahead { mm_virtual_rbuf_t *buf; }; static inline void od_readahead_init(od_readahead_t *readahead) { readahead->buf = NULL; } void od_readahead_free(od_readahead_t *readahead); int od_readahead_prepare(od_readahead_t *readahead); static inline size_t od_readahead_capacity(od_readahead_t *readahead) { od_assert(readahead->buf); return mm_virtual_rbuf_capacity(readahead->buf); } static inline int od_readahead_left(od_readahead_t *readahead) { od_assert(readahead->buf); return mm_virtual_rbuf_free_size(readahead->buf); } static inline int od_readahead_unread(od_readahead_t *readahead) { return mm_virtual_rbuf_size(readahead->buf); } static inline int od_readahead_next_byte_is(od_readahead_t *readahead, uint8_t value) { struct iovec rvec = mm_virtual_rbuf_read_begin(readahead->buf); if (rvec.iov_len == 0) { /* no bytes in buf */ return 0; } /* no read commit - just check first byte */ return ((uint8_t *)rvec.iov_base)[0] == value; } static inline struct iovec od_readahead_read_begin(od_readahead_t *readahead) { return mm_virtual_rbuf_read_begin(readahead->buf); } static inline void od_readahead_read_commit(od_readahead_t *readahead, size_t count) { mm_virtual_rbuf_read_commit(readahead->buf, count); } static inline size_t od_readahead_read(od_readahead_t *readahead, char *dest, size_t max) { return mm_virtual_rbuf_read(readahead->buf, dest, max); } static inline struct iovec od_readahead_write_begin(od_readahead_t *readahead) { return mm_virtual_rbuf_write_begin(readahead->buf); } static inline void od_readahead_write_commit(od_readahead_t *readahead, size_t count) { mm_virtual_rbuf_write_commit(readahead->buf, count); } void od_readahead_describe(od_readahead_t *readahead, char *buf, size_t buf_size); odyssey-1.6.0-rc1/sources/include/relay.h000066400000000000000000000030011526064202200202640ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include typedef struct { machine_msg_t *msg; } od_xbuf_msg_t; struct od_relay_xbuf { mm_vector_t msgs; }; struct od_relay { od_client_t *client; od_relay_xbuf_t xbuf; od_xplan_t xplan; /* additional message to handle in copy stream */ machine_msg_t *copy_additional; }; void od_relay_init(od_relay_t *relay, od_client_t *client); void od_relay_destroy(od_relay_t *relay); static inline void od_relay_set_copy_additional(od_relay_t *relay, machine_msg_t *msg) { relay->copy_additional = msg; } static inline machine_msg_t *od_relay_get_copy_additional(od_relay_t *relay) { return relay->copy_additional; } od_frontend_status_t od_relay_process_query(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms); od_frontend_status_t od_relay_process_fcall(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms); od_frontend_status_t od_relay_process_xmsg(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms); od_frontend_status_t od_relay_process_xflush(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms); od_frontend_status_t od_relay_process_xsync(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms); extern uint8_t od_relay_deferred_begin_bytes[12]; odyssey-1.6.0-rc1/sources/include/reset.h000066400000000000000000000002241526064202200202760ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_reset(od_server_t *); odyssey-1.6.0-rc1/sources/include/restart_sync.h000066400000000000000000000007611526064202200217020ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include /* * get parent odyssey pid if current odyssey was started during online restart * -1 otherwise * * should be called only on start of odyssey, because it uses plain envp * (which may be changed by setproctitle) */ pid_t od_restart_get_ppid(void); pid_t od_restart_run_new_binary(void); void od_restart_terminate_parent(void); DEFINE_SIMPLE_MODULE_LOGGER(online_restart, "online-restart") odyssey-1.6.0-rc1/sources/include/route.h000066400000000000000000000213251526064202200203170ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct { atomic_int_fast64_t refs; bool is_null; char value[]; } od_route_pswd_t; od_route_pswd_t *od_route_pswd_create(const char *value); od_route_pswd_t *od_route_pswd_ref(od_route_pswd_t *p); void od_route_pswd_unref(od_route_pswd_t *p); struct od_route { od_rule_t *rule; od_route_id_t id; od_stat_t stats; od_stat_t stats_prev; bool stats_mark_db; /* * only one must be non NULL */ od_multi_pool_t *exclusive_pool; od_shared_pool_t *shared_pool; od_client_pool_t client_pool; kiwi_params_lock_t params; int64_t tcp_connections; mm_mutex_t lock; od_error_logger_t *err_logger; bool extra_logging_enabled; od_list_t link; struct { od_route_pswd_t *password; uint64_t valid_until_ms; /* password was set at least once */ mm_wait_flag_t *ready; int refresh_in_progress; } auth_query_cache; }; static inline int od_route_has_shared_pool(od_route_t *route) { od_assert((route->shared_pool != NULL) == (route->exclusive_pool == NULL)); return route->shared_pool != NULL; } static inline int od_route_has_exclusive_pool(od_route_t *route) { od_assert((route->exclusive_pool != NULL) == (route->shared_pool == NULL)); return route->exclusive_pool != NULL; } static inline od_multi_pool_t *od_route_server_pools(od_route_t *route) { if (od_route_has_exclusive_pool(route)) { od_assert(route->shared_pool == NULL); return route->exclusive_pool; } od_assert(route->exclusive_pool == NULL); return route->shared_pool->mpool; } static inline int od_multi_pool_filter_by_route(void *arg, const od_multi_pool_key_t *key) { od_route_t *route = arg; if (key->dbname == NULL || strcmp(route->id.database, key->dbname) != 0) { return 0; } if (key->username == NULL || strcmp(route->id.user, key->username) != 0) { return 0; } return 1; } static inline od_server_t * od_route_server_pool_foreach_locked(od_route_t *route, od_server_state_t state, od_server_pool_cb_t callback, void **argv) { if (route->shared_pool == NULL) { return od_multi_pool_foreach_locked(route->exclusive_pool, NULL, NULL, state, callback, argv); } return od_multi_pool_foreach_locked(route->shared_pool->mpool, od_multi_pool_filter_by_route, route, state, callback, argv); } static inline int od_route_server_pool_count_active_locked(od_route_t *route, int force_id) { if (route->shared_pool == NULL) { return od_multi_pool_count_active_locked(route->exclusive_pool, NULL, NULL); } if (force_id) { return od_multi_pool_count_active_locked( route->shared_pool->mpool, od_multi_pool_filter_by_route, route); } return od_multi_pool_count_active_locked(route->shared_pool->mpool, NULL, NULL); } static inline int od_route_server_pool_count_idle_locked(od_route_t *route, int force_id) { if (route->shared_pool == NULL) { return od_multi_pool_count_idle_locked(route->exclusive_pool, NULL, NULL); } if (force_id) { return od_multi_pool_count_idle_locked( route->shared_pool->mpool, od_multi_pool_filter_by_route, route); } return od_multi_pool_count_idle_locked(route->shared_pool->mpool, NULL, NULL); } static inline int od_route_server_pool_count_total_locked(od_route_t *route, int force_id) { if (route->shared_pool == NULL) { return od_multi_pool_total_locked(route->exclusive_pool, NULL, NULL); } if (force_id) { return od_multi_pool_total_locked(route->shared_pool->mpool, od_multi_pool_filter_by_route, route); } return od_multi_pool_total_locked(route->shared_pool->mpool, NULL, NULL); } static inline int od_route_init(od_route_t *route, od_shared_pool_t *shared_pool, bool extra_route_logging) { route->rule = NULL; route->tcp_connections = 0; od_route_id_init(&route->id); route->shared_pool = NULL; route->exclusive_pool = NULL; if (shared_pool == NULL) { route->exclusive_pool = od_multi_pool_create(od_pg_server_pool_free); if (route->exclusive_pool == NULL) { return NOT_OK_RESPONSE; } } else { route->shared_pool = od_shared_pool_ref(shared_pool); } od_client_pool_init(&route->client_pool); /* stat init */ route->stats_mark_db = false; route->extra_logging_enabled = extra_route_logging; if (extra_route_logging) { /* error logging */ route->err_logger = od_err_logger_create_default(); } else { route->err_logger = NULL; } memset(&route->auth_query_cache, 0, sizeof(route->auth_query_cache)); route->auth_query_cache.ready = mm_wait_flag_create(); if (route->auth_query_cache.ready == NULL) { od_multi_pool_destroy(route->exclusive_pool); return NOT_OK_RESPONSE; } od_stat_init(&route->stats); od_stat_init(&route->stats_prev); kiwi_params_lock_init(&route->params); od_list_init(&route->link); mm_mutex_init(&route->lock); return OK_RESPONSE; } static inline void od_route_free(od_route_t *route) { if (route->rule) { od_rules_unref(route->rule); } od_route_id_free(&route->id); if (route->exclusive_pool != NULL) { od_multi_pool_destroy(route->exclusive_pool); } else { od_shared_pool_unref(route->shared_pool); } kiwi_params_lock_free(&route->params); if (route->stats.enable_quantiles) { od_stat_free(&route->stats); } if (route->extra_logging_enabled) { od_err_logger_free(route->err_logger); route->err_logger = NULL; } od_route_pswd_unref(route->auth_query_cache.password); mm_wait_flag_destroy(route->auth_query_cache.ready); mm_mutex_destroy(&route->lock); od_free(route); } static inline od_route_t *od_route_allocate(od_shared_pool_t *shared_pool) { od_route_t *route = od_malloc(sizeof(od_route_t)); if (route == NULL) { return NULL; } if (od_route_init(route, shared_pool, true /* extra_route_logging */) != OK_RESPONSE) { od_route_free(route); return NULL; } return route; } static inline void od_route_lock(od_route_t *route) { mm_mutex_lock2(&route->lock); od_multi_pool_lock(od_route_server_pools(route)); } static inline void od_route_unlock(od_route_t *route) { od_multi_pool_unlock(od_route_server_pools(route)); mm_mutex_unlock(&route->lock); } static inline int od_route_is_dynamic(od_route_t *route) { return route->rule->db_is_default || route->rule->user_is_default; } static inline int od_route_match_compare_client_cb(od_client_t *client, void **argv) { return od_id_cmp(&client->id, argv[0]); } static inline od_client_t *od_route_match_client(od_route_t *route, od_id_t *id) { void *argv[] = { id }; od_client_t *match; match = od_client_pool_foreach(&route->client_pool, OD_CLIENT_ACTIVE, od_route_match_compare_client_cb, argv); if (match) { return match; } match = od_client_pool_foreach(&route->client_pool, OD_CLIENT_QUEUE, od_route_match_compare_client_cb, argv); if (match) { return match; } match = od_client_pool_foreach(&route->client_pool, OD_CLIENT_PENDING, od_route_match_compare_client_cb, argv); if (match) { return match; } return NULL; } static inline void od_route_kill_client(od_route_t *route, od_id_t *id) { od_client_t *client; client = od_route_match_client(route, id); if (client) { od_client_kill(client); } } static inline int od_route_kill_cb(od_client_t *client, void **argv) { (void)argv; od_client_kill(client); return 0; } static inline void od_route_kill_client_pool(od_route_t *route) { od_client_pool_foreach(&route->client_pool, OD_CLIENT_ACTIVE, od_route_kill_cb, NULL); od_client_pool_foreach(&route->client_pool, OD_CLIENT_PENDING, od_route_kill_cb, NULL); od_client_pool_foreach(&route->client_pool, OD_CLIENT_QUEUE, od_route_kill_cb, NULL); } static inline uint64_t od_route_pools_version(od_route_t *route) { od_multi_pool_t *mpool = od_route_server_pools(route); return od_multi_pool_version(mpool); } int od_route_wait(od_route_t *route, od_client_t *client, const od_address_t *address, uint64_t version, uint32_t timeout_ms); void od_route_signal_locked(od_route_t *route, od_server_t *server); int od_route_server_pool_can_add_locked(od_route_t *route, od_multi_pool_element_t *el); int od_route_server_pool_total(od_route_t *route, od_multi_pool_element_t *el); int od_route_server_pool_next_idle_locked(od_route_t *route, od_storage_endpoint_t *endpoint, od_server_t **server); od_multi_pool_element_t * od_route_get_server_pool_element_locked(od_route_t *route, const od_address_t *address); odyssey-1.6.0-rc1/sources/include/route_id.h000066400000000000000000000031031526064202200207650ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include typedef struct od_route_id od_route_id_t; struct od_route_id { char *user; int user_len; char *database; int database_len; bool physical_rep; bool logical_rep; }; static inline void od_route_id_init(od_route_id_t *id) { id->user = NULL; id->user_len = 0; id->database = NULL; id->database_len = 0; id->physical_rep = false; id->logical_rep = false; } static inline void od_route_id_free(od_route_id_t *id) { if (id->database) { od_free(id->database); } if (id->user) { od_free(id->user); } } static inline int od_route_id_copy(od_route_id_t *dest, od_route_id_t *id) { dest->database = od_malloc(id->database_len); if (dest->database == NULL) { return -1; } memcpy(dest->database, id->database, id->database_len); dest->database_len = id->database_len; dest->user = od_malloc(id->user_len); if (dest->user == NULL) { od_free(dest->database); dest->database = NULL; return -1; } memcpy(dest->user, id->user, id->user_len); dest->user_len = id->user_len; dest->physical_rep = id->physical_rep; dest->logical_rep = id->logical_rep; return 0; } static inline int od_route_id_compare(od_route_id_t *a, od_route_id_t *b) { if (a->database_len == b->database_len && a->user_len == b->user_len) { if (memcmp(a->database, b->database, a->database_len) == 0 && memcmp(a->user, b->user, a->user_len) == 0 && a->logical_rep == b->logical_rep) { if (a->physical_rep == b->physical_rep) { return 1; } } } return 0; } odyssey-1.6.0-rc1/sources/include/route_pool.h000066400000000000000000000144701526064202200213530ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #ifdef PROM_FOUND #include #endif typedef int (*od_route_pool_stat_cb_t)(od_route_t *route, od_stat_t *current, od_stat_t *avg, #ifdef PROM_FOUND od_prom_metrics_t *metrics, #endif void **argv); typedef int (*od_route_pool_stat_database_cb_t)(char *database, int database_len, od_stat_t *total, od_stat_t *avg, void **argv); typedef od_retcode_t (*od_route_pool_stat_route_error_cb_t)( od_error_logger_t *l, void **argv); typedef int (*od_route_pool_cb_t)(od_route_t *, void **); typedef struct od_route_pool od_route_pool_t; struct od_route_pool { /* used for counting error for client without concrete route * like default_db.usr1, db1.default, etc * */ od_error_logger_t *err_logger; int count; mm_mutex_t lock; od_list_t list; }; static inline void od_route_pool_lock(od_route_pool_t *route_pool) { mm_mutex_lock2(&route_pool->lock); } static inline void od_route_pool_unlock(od_route_pool_t *route_pool) { mm_mutex_unlock(&route_pool->lock); } typedef od_retcode_t (*od_route_pool_stat_frontend_error_cb_t)( od_route_pool_t *pool, void **argv); static inline void od_route_pool_init(od_route_pool_t *pool) { od_list_init(&pool->list); pool->err_logger = od_err_logger_create_default(); pool->count = 0; mm_mutex_init(&pool->lock); } static inline void od_route_pool_free(od_route_pool_t *pool) { if (pool == NULL) { return; } mm_mutex_destroy(&pool->lock); od_err_logger_free(pool->err_logger); od_list_t *i, *n; od_list_foreach_safe (&pool->list, i, n) { od_route_t *route; route = od_container_of(i, od_route_t, link); od_route_free(route); } } static inline od_route_t *od_route_pool_new(od_route_pool_t *pool, od_route_id_t *id, od_rule_t *rule) { od_route_t *route = od_route_allocate(rule->shared_pool); if (route == NULL) { return NULL; } int rc; rc = od_route_id_copy(&route->id, id); if (rc == -1) { od_route_free(route); return NULL; } route->rule = rule; od_rules_ref(rule); if (rule->quantiles_count) { route->stats.enable_quantiles = true; for (size_t i = 0; i < QUANTILES_WINDOW; ++i) { route->stats.transaction_hgram[i] = td_new(QUANTILES_COMPRESSION); route->stats.query_hgram[i] = td_new(QUANTILES_COMPRESSION); } } od_list_append(&pool->list, &route->link); pool->count++; return route; } static inline int od_route_pool_foreach(od_route_pool_t *pool, od_route_pool_cb_t callback, void **argv) { od_list_t *i, *n; od_list_foreach_safe (&pool->list, i, n) { od_route_t *route; route = od_container_of(i, od_route_t, link); int rc; rc = callback(route, argv); if (rc == -1) { return -1; } if (rc) { return 1; } } return 0; } static inline od_route_t * od_route_pool_match(od_route_pool_t *pool, od_route_id_t *key, od_rule_t *rule) { od_list_t *i; od_list_foreach (&pool->list, i) { od_route_t *route; route = od_container_of(i, od_route_t, link); if (route->rule == rule && od_route_id_compare(&route->id, key)) { return route; } } return NULL; } static inline void od_route_pool_stat(od_route_pool_t *pool, uint64_t prev_time_us, #ifdef PROM_FOUND od_prom_metrics_t *metrics, #endif od_route_pool_stat_cb_t callback, void **argv) { od_list_t *i; od_list_foreach (&pool->list, i) { od_route_t *route; uint8_t next_tdigest; route = od_container_of(i, od_route_t, link); od_stat_t current; od_stat_init(¤t); od_stat_copy(¤t, &route->stats); /* calculate average */ od_stat_t avg; od_stat_init(&avg); if (route->stats.enable_quantiles) { uint8_t current_tdigest = route->stats.current_tdigest; next_tdigest = (current_tdigest + 1) % QUANTILES_WINDOW; td_reset(route->stats.transaction_hgram[next_tdigest]); td_reset(route->stats.query_hgram[next_tdigest]); route->stats.current_tdigest = next_tdigest; } od_stat_average(&avg, ¤t, &route->stats_prev, prev_time_us); /* update route stats */ od_stat_update(&route->stats_prev, ¤t); if (callback) { callback(route, ¤t, &avg, #ifdef PROM_FOUND metrics, #endif argv); } } } static inline void od_route_pool_stat_database_mark(od_route_pool_t *pool, char *database, int database_len, od_stat_t *current, od_stat_t *prev) { od_list_t *i; od_list_foreach (&pool->list, i) { od_route_t *route; route = od_container_of(i, od_route_t, link); if (route->stats_mark_db) { continue; } if (route->id.database_len != database_len) { continue; } if (memcmp(route->id.database, database, database_len) != 0) { continue; } od_stat_sum(current, &route->stats); od_stat_sum(prev, &route->stats_prev); route->stats_mark_db = true; } } static inline void od_route_pool_stat_unmark_db(od_route_pool_t *pool) { od_route_t *route; od_list_t *i; od_list_foreach (&pool->list, i) { route = od_container_of(i, od_route_t, link); route->stats_mark_db = false; } } static inline int od_route_pool_stat_database(od_route_pool_t *pool, od_route_pool_stat_database_cb_t callback, uint64_t prev_time_us, void **argv) { od_route_t *route; od_list_t *i; od_list_foreach (&pool->list, i) { route = od_container_of(i, od_route_t, link); if (route->stats_mark_db) { continue; } /* gather current and previous cron stats */ od_stat_t current; od_stat_t prev; od_stat_init(¤t); od_stat_init(&prev); od_route_pool_stat_database_mark(pool, route->id.database, route->id.database_len, ¤t, &prev); /* calculate average */ od_stat_t avg; od_stat_init(&avg); od_stat_average(&avg, ¤t, &prev, prev_time_us); int rc; rc = callback(route->id.database, route->id.database_len - 1, ¤t, &avg, argv); if (rc == -1) { od_route_pool_stat_unmark_db(pool); return -1; } } od_route_pool_stat_unmark_db(pool); return 0; } static inline od_retcode_t od_route_pool_stat_err_frontend(od_route_pool_t *pool, od_route_pool_stat_frontend_error_cb_t callback, void **argv) { return callback(pool, argv); } odyssey-1.6.0-rc1/sources/include/router.h000066400000000000000000000036771526064202200205130ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include struct od_router { mm_mutex_t lock; od_rules_t rules; od_route_pool_t route_pool; /* clients */ atomic_uint_fast64_t clients; /* servers */ atomic_uint_fast64_t servers_routing; /* error logging */ od_error_logger_t *router_err_logger; /* global */ od_global_t *global; /* router has type of list */ od_list_t servers; }; static inline void od_router_lock(od_router_t *router) { mm_mutex_lock2(&router->lock); } static inline void od_router_unlock(od_router_t *router) { mm_mutex_unlock(&router->lock); } void od_router_init(od_router_t *, od_global_t *); void od_router_free(od_router_t *); int od_router_reconfigure(od_router_t *, od_rules_t *); int od_router_expire(od_router_t *, od_list_t *); void od_router_keep_min_pool_size_step(od_router_t *); void od_router_gc(od_router_t *); void od_router_stat(od_router_t *, uint64_t, #ifdef PROM_FOUND od_prom_metrics_t *, #endif od_route_pool_stat_cb_t, void **); int od_router_foreach(od_router_t *, od_route_pool_cb_t, void **); od_router_status_t od_router_route(od_router_t *router, od_client_t *client); void od_router_unroute(od_router_t *, od_client_t *); od_router_status_t od_router_attach(od_router_t *, od_client_t *, bool, od_storage_endpoint_t *, int immediate); /* returns 1 if someone was signalled, 0 otherwise */ void od_router_detach(od_router_t *, od_client_t *); void od_router_close(od_router_t *, od_client_t *); od_router_status_t od_router_cancel(od_router_t *, kiwi_key_t *, od_router_cancel_t *); void od_router_kill(od_router_t *, od_id_t *); static inline int od_route_pool_stat_err_router(od_router_t *router, od_route_pool_stat_route_error_cb_t callback, void **argv) { return callback(router->router_err_logger, argv); } odyssey-1.6.0-rc1/sources/include/router_cancel.h000066400000000000000000000011721526064202200220040ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include typedef struct { od_id_t id; od_rule_storage_t *storage; const od_address_t *address; kiwi_key_t key; od_server_t *server; } od_router_cancel_t; static inline void od_router_cancel_init(od_router_cancel_t *cancel) { cancel->storage = NULL; cancel->address = NULL; cancel->server = NULL; kiwi_key_init(&cancel->key); } static inline void od_router_cancel_free(od_router_cancel_t *cancel) { if (cancel->storage) { od_rules_storage_unref(cancel->storage); } } odyssey-1.6.0-rc1/sources/include/rules.h000066400000000000000000000160761526064202200203220ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { OD_RULE_AUTH_UNDEF, OD_RULE_AUTH_NONE, OD_RULE_AUTH_BLOCK, OD_RULE_AUTH_CLEAR_TEXT, OD_RULE_AUTH_EXTERNAL, OD_RULE_AUTH_MD5, OD_RULE_AUTH_SCRAM_SHA_256, OD_RULE_AUTH_CERT } od_rule_auth_type_t; /* * Connection type for rule matching * just like in https://www.postgresql.org/docs/current/auth-pg-hba-conf.html * Currently only any (default), local, host, hostssl, hostnossl are supported */ typedef enum { OD_RULE_CONN_TYPE_DEFAULT = 0, /* any connection type */ OD_RULE_CONN_TYPE_LOCAL, /* unix socket connections */ OD_RULE_CONN_TYPE_HOST, /* ssl or no-ssl connection */ OD_RULE_CONN_TYPE_HOSTSSL, /* only ssl connections */ OD_RULE_CONN_TYPE_HOSTNOSSL, /* only non-ssl connections */ } od_rule_conn_type_t; const char *od_rule_conn_type_to_str(od_rule_conn_type_t ct); typedef struct { od_rule_t *rule; machine_wait_flag_t *done_flag; } od_group_checker_run_args; struct od_rule_auth { char *common_name; od_list_t link; }; typedef enum { OD_RULE_ROLE_ADMIN, OD_RULE_ROLE_STAT, OD_RULE_ROLE_NOTALLOW, OD_RULE_ROLE_UNDEF, } od_rule_role_type_t; typedef struct od_rule_key od_rule_key_t; struct od_rule_key { char *usr_name; char *db_name; od_address_range_t address_range; od_rule_conn_type_t conn_type; od_list_t link; }; static inline void od_rule_key_init(od_rule_key_t *rk) { od_list_init(&rk->link); rk->usr_name = NULL; rk->db_name = NULL; rk->address_range = od_address_range_create_default(); rk->conn_type = OD_RULE_CONN_TYPE_DEFAULT; } static inline void od_rule_key_free(od_rule_key_t *rk) { od_list_unlink(&rk->link); if (rk->db_name != NULL) { od_free(rk->db_name); } if (rk->usr_name != NULL) { od_free(rk->usr_name); } od_address_range_destroy(&rk->address_range); od_free(rk); } struct od_rule { atomic_int_fast64_t refs; /* versioning */ int mark; int obsolete; int order; /* id */ char *db_name; int db_name_len; int db_is_default; char *user_name; int user_name_len; int user_is_default; od_address_range_t address_range; od_rule_role_type_t user_role; od_rule_conn_type_t conn_type; /* auth */ char *auth; od_rule_auth_type_t auth_mode; char *auth_query; char *auth_query_db; char *auth_query_user; int auth_common_name_default; od_list_t auth_common_names; int auth_common_names_count; int enable_mdb_iamproxy_auth; char *mdb_iamproxy_socket_path; #ifdef PAM_FOUND /* PAM parameters */ char *auth_pam_service; od_pam_auth_data_t *auth_pam_data; #endif #ifdef LDAP_FOUND char *ldap_endpoint_name; od_ldap_endpoint_t *ldap_endpoint; int ldap_pool_timeout; int ldap_pool_size; int ldap_pool_ttl; od_list_t ldap_storage_creds_list; char *ldap_storage_credentials_attr; #endif char *auth_module; char *password; int password_len; od_rule_storage_t *storage; char *storage_name; char *storage_db; char *storage_user; int storage_user_len; char *storage_password; int storage_password_len; /* pool */ od_rule_pool_t *pool; int catchup_timeout; int catchup_checks; od_shared_pool_t *shared_pool; /* Should we deploy user GUCS when attaching? */ int maintain_params; /* group */ od_group_t *group; /* set if rule is group */ char **user_names; int users_in_group; /* PostgreSQL options */ kiwi_vars_t vars; /* misc */ int client_fwd_error; int client_show_id; int reserve_session_server_connection; int application_name_add_host; int client_max_set; int client_max; int log_debug; int log_query; int enable_password_passthrough; double *quantiles; int quantiles_count; int server_drop_on_cached_plan_error; int server_drop_on_oom; uint64_t server_lifetime_us; int keepalive; int keepalive_keep_interval; int keepalive_probes; int keepalive_usr_timeout; od_target_session_attrs_t target_session_attrs; /* some settings that we are going to force-on while backend connection acquiring */ untyped_kiwi_var_t *backend_startup_vars; size_t backend_startup_vars_sz; od_list_t link; int64_t group_checker_machine_id; machine_wait_flag_t *group_checker_online; machine_wait_flag_t *group_checker_finished; }; struct od_rules { mm_mutex_t mu; od_list_t storages; #ifdef LDAP_FOUND od_list_t ldap_endpoints; #endif od_list_t rules; int next_order; }; static inline void od_rules_lock(od_rules_t *rules) { mm_mutex_lock2(&rules->mu); } static inline void od_rules_unlock(od_rules_t *rules) { mm_mutex_unlock(&rules->mu); } /* rules */ void od_rules_init(od_rules_t *); void od_rules_free(od_rules_t *); int od_rules_validate(od_rules_t *, od_config_t *, od_logger_t *); /* auto-generate default rules (for auth query etc) if needed */ int od_rules_autogenerate_defaults(od_rules_t *rules, od_logger_t *logger); int od_rules_merge(od_rules_t *, od_rules_t *, od_list_t *added, od_list_t *deleted, od_list_t *drop, od_list_t *not_changed); void od_rules_print(od_rules_t *, od_logger_t *); void od_rules_stop_checkers(od_rules_t *rules); int od_rules_cleanup(od_rules_t *rules); int od_rules_storage_merge(od_rules_t *dst, od_rules_t *src); int od_rules_sort_for_matching(od_rules_t *rules); /* rule */ od_rule_t *od_rules_add_new_rule(od_rules_t *rules, const char *dbname, int db_is_default, const char *user, int user_is_default, const od_address_range_t *address_range, od_rule_conn_type_t conn_type, int pool_internal); void od_rules_ref(od_rule_t *); void od_rules_unref(od_rule_t *); int od_rules_compare(od_rule_t *, od_rule_t *); od_rule_t *od_rules_forward(od_rules_t *, const kiwi_be_startup_t *startup, struct sockaddr_storage *, int); /* search rule with desored characteristik */ od_rule_t *od_rules_match(od_rules_t *rules, const char *db_name, const char *user_name, const od_address_range_t *address_range, od_rule_conn_type_t conn_type, int db_is_default, int user_is_default, int pool_internal); /* group */ od_group_t *od_rules_group_allocate(od_global_t *global); void od_rules_rule_free(od_rule_t *rule); /* storage API */ od_rule_storage_t *od_rules_storage_match(od_rules_t *, const char *); od_rule_storage_t *od_rules_storage_add(od_rules_t *rules, od_rule_storage_t *storage); int od_rules_storage_compare(od_rule_storage_t *a, od_rule_storage_t *b); od_retcode_t od_rules_storages_watchdogs_run(od_logger_t *logger, od_rules_t *rules); #ifdef LDAP_FOUND /* ldap endpoint */ od_ldap_endpoint_t *od_rules_ldap_endpoint_add(od_rules_t *rules, od_ldap_endpoint_t *ldap); od_ldap_storage_credentials_t * od_rule_ldap_storage_credentials_add(od_rule_t *rule, od_ldap_storage_credentials_t *lsc); #endif /* auth */ od_rule_auth_t *od_rules_auth_add(od_rule_t *); void od_rules_auth_free(od_rule_auth_t *); od_retcode_t od_rules_groups_checkers_run(od_logger_t *logger, od_rules_t *rules); bool od_name_in_rule(const od_rule_t *rule, const char *name); odyssey-1.6.0-rc1/sources/include/sasl.h000066400000000000000000000001661526064202200201230ustar00rootroot00000000000000#pragma once typedef enum { OD_SASL_ERROR_AUTH_IDENTITY = -4, OD_SASL_ERROR_MANDATORY_EXT = -5, } od_sasl_error_t; odyssey-1.6.0-rc1/sources/include/scram.h000066400000000000000000000047741526064202200202770ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #define OD_SCRAM_MAX_KEY_LEN SCRAM_MAX_KEY_LEN #define OD_SCRAM_SHA_256_DEFAULT_ITERATIONS SCRAM_SHA_256_DEFAULT_ITERATIONS typedef struct od_scram_state od_scram_state_t; struct od_scram_state { char *client_nonce; char *client_first_message; char *client_final_message; char *server_nonce; char *server_first_message; uint8_t *salted_password; int iterations; char *salt; int use_passthrough_keys; uint8_t stored_key[OD_SCRAM_MAX_KEY_LEN]; uint8_t server_key[OD_SCRAM_MAX_KEY_LEN]; uint8_t client_key[OD_SCRAM_MAX_KEY_LEN]; }; static inline void od_scram_state_init(od_scram_state_t *state) { memset(state, 0, sizeof(*state)); } static inline void od_scram_state_free(od_scram_state_t *state) { od_free(state->client_nonce); od_free(state->client_first_message); od_free(state->client_final_message); if (state->server_nonce) { od_free(state->server_nonce); } if (state->server_first_message) { od_free(state->server_first_message); } od_free(state->salted_password); od_free(state->salt); memset(state, 0, sizeof(*state)); } machine_msg_t * od_scram_create_client_first_message(od_scram_state_t *scram_state); machine_msg_t * od_scram_create_client_final_message(od_scram_state_t *scram_state, char *password, char *auth_data, size_t auth_data_size); machine_msg_t * od_scram_create_server_first_message(od_scram_state_t *scram_state); machine_msg_t * od_scram_create_server_final_message(od_scram_state_t *scram_state); int od_scram_verify_server_signature(od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size); int od_scram_verify_final_nonce(od_scram_state_t *scram_state, char *final_nonce, size_t final_nonce_size); int od_scram_verify_client_proof(od_scram_state_t *scram_state, uint8_t *client_proof); int od_scram_parse_verifier(od_scram_state_t *scram_state, char *verifier); int od_scram_init_from_plain_password(od_scram_state_t *scram_state, char *plain_password); int od_scram_read_client_first_message(od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size); int od_scram_read_client_final_message(mm_io_t *io, od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size, char **final_nonce_ptr, size_t *final_nonce_size_ptr, uint8_t **proof_ptr); odyssey-1.6.0-rc1/sources/include/server.h000066400000000000000000000105701526064202200204670ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include typedef enum { OD_SERVER_UNDEF, OD_SERVER_IDLE, OD_SERVER_ACTIVE, } od_server_state_t; struct od_server { /* * reference counter for backend connection * * we consider the following as reference: * - functions od_server_create and od_server_free * (smth like "the pool holds the ref") * - client connection that use the server * (od_server_attach_client and od_server_dettach_client) * - each currently running cancel on the server * (od_server_cancel_begin and od_server_cancel_end) * * every server that have ref counter > 1 * should have ACTIVE state, * to prevent sending cancel to wrong client * * not for directly usage */ atomic_int_fast64_t refs; od_server_state_t state; od_scram_state_t scram_state; od_id_t id; machine_tls_t *tls; od_io_t io; int is_transaction; int is_error_tx; /* Copy stmt state */ int copy_mode; /**/ int deploy_sync; od_stat_state_t stats_state; od_multi_pool_element_t *pool_element; od_storage_endpoint_t *endpoint; uint64_t sync_request; uint64_t sync_reply; int msg_broken; int idle_time; kiwi_key_t key; kiwi_key_t key_client; kiwi_vars_t vars; machine_msg_t *error_connect; /* do not set this field directly, use od_server_attach_client */ od_client_t *client; od_route_t *route; od_global_t *global; int offline; uint64_t init_time_us; bool synced_settings; od_list_t link; /* xproto state fields */ int xproto_mode; int xproto_err; int cached_plan_broken; int oom; mm_hashmap_t *prep_stmts; /* SIEVE eviction queue over the reserved statements */ od_list_t pstmt_fifo; od_list_t *pstmt_hand; size_t pstmt_count; int need_startup; }; static inline void od_server_init(od_server_t *server, int reserve_prep_stmts) { memset(server, 0, sizeof(od_server_t)); server->state = OD_SERVER_UNDEF; atomic_init(&server->refs, 1); server->route = NULL; server->client = NULL; server->global = NULL; server->tls = NULL; server->idle_time = 0; server->is_transaction = 0; server->is_error_tx = 0; server->copy_mode = 0; server->deploy_sync = 0; server->sync_request = 0; server->sync_reply = 0; server->init_time_us = machine_time_us(); server->error_connect = NULL; server->offline = 0; server->synced_settings = false; server->pool_element = NULL; server->endpoint = NULL; server->need_startup = 1; server->msg_broken = 0; server->oom = 0; od_stat_state_init(&server->stats_state); od_scram_state_init(&server->scram_state); kiwi_key_init(&server->key); kiwi_key_init(&server->key_client); kiwi_vars_init(&server->vars); od_io_init(&server->io); od_list_init(&server->link); od_list_init(&server->pstmt_fifo); server->pstmt_hand = &server->pstmt_fifo; server->pstmt_count = 0; memset(&server->id, 0, sizeof(server->id)); server->xproto_mode = 0; server->xproto_err = 0; server->cached_plan_broken = 0; if (reserve_prep_stmts) { server->prep_stmts = od_server_pstmt_hashmap_create(); } else { server->prep_stmts = NULL; } } void od_server_attach_client(od_server_t *server, od_client_t *client); void od_server_detach_client(od_server_t *server); static inline od_server_t *od_server_allocate(int reserve_prep_stmts) { od_server_t *server = od_malloc(sizeof(od_server_t)); if (server == NULL) { return NULL; } od_server_init(server, reserve_prep_stmts); return server; } void od_server_free(od_server_t *server); static inline void od_server_sync_request(od_server_t *server, uint64_t count) { server->sync_request += count; } static inline void od_server_sync_reply(od_server_t *server) { server->sync_reply++; } static inline int od_server_in_deploy(od_server_t *server) { return server->deploy_sync > 0; } static inline int od_server_synchronized(od_server_t *server) { od_assert(server->sync_request >= server->sync_reply); return server->sync_request == server->sync_reply; } od_server_pool_t *od_server_pool(od_server_t *server); const od_address_t *od_server_pool_address(od_server_t *server); void od_server_set_pool_state(od_server_t *server, od_server_state_t state); void od_server_cancel_begin(od_server_t *server); void od_server_cancel_end(od_server_t *server); odyssey-1.6.0-rc1/sources/include/server_pool.h000066400000000000000000000140531526064202200215200ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include typedef int (*od_server_pool_cb_t)(od_server_t *, void **); struct od_server_pool { od_list_t active; od_list_t idle; int count_active; int count_idle; }; static inline void od_server_pool_init(od_server_pool_t *pool) { pool->count_active = 0; pool->count_idle = 0; od_list_init(&pool->idle); od_list_init(&pool->active); } #define OD_SERVER_POOL_FREE_DECLARE(name, type, server_free_cb) \ static inline void od_##name##_server_pool_free( \ od_server_pool_t *pool) \ { \ type *server; \ od_list_t *i, *n; \ \ od_list_foreach_safe (&pool->idle, i, n) { \ server = od_container_of(i, type, link); \ server_free_cb(server); \ } \ \ od_list_foreach_safe (&pool->active, i, n) { \ server = od_container_of(i, type, link); \ server_free_cb(server); \ } \ } OD_SERVER_POOL_FREE_DECLARE(pg, od_server_t, od_server_free) #define OD_SERVER_POOL_SET_DECLARE(name, type) \ static inline void od_##name##_server_pool_set( \ od_server_pool_t *pool, type *server, od_server_state_t state) \ { \ if (server->state == state) \ return; \ switch (server->state) { \ case OD_SERVER_UNDEF: \ break; \ case OD_SERVER_IDLE: \ pool->count_idle--; \ break; \ case OD_SERVER_ACTIVE: \ pool->count_active--; \ break; \ } \ \ od_list_t *target = NULL; \ switch (state) { \ case OD_SERVER_UNDEF: \ break; \ case OD_SERVER_IDLE: \ target = &pool->idle; \ pool->count_idle++; \ break; \ case OD_SERVER_ACTIVE: \ target = &pool->active; \ pool->count_active++; \ break; \ } \ \ od_list_unlink(&server->link); \ od_list_init(&server->link); \ if (target) { \ od_list_push(target, &server->link); \ } \ server->state = state; \ } OD_SERVER_POOL_SET_DECLARE(pg, od_server_t) #define OD_SERVER_POOL_NEXT_DECLARE(name, type) \ static inline type *od_##name##_server_pool_next( \ od_server_pool_t *pool, od_server_state_t state) \ { \ int target_count = 0; \ od_list_t *target = NULL; \ switch (state) { \ case OD_SERVER_IDLE: \ target_count = pool->count_idle; \ target = &pool->idle; \ break; \ case OD_SERVER_ACTIVE: \ target_count = pool->count_active; \ target = &pool->active; \ break; \ case OD_SERVER_UNDEF: \ od_assert(0); \ break; \ } \ if (target_count == 0) \ return NULL; \ type *server; \ server = od_container_of(target->next, type, link); \ return server; \ } OD_SERVER_POOL_NEXT_DECLARE(pg, od_server_t) static inline od_server_t *od_server_pool_foreach(od_server_pool_t *pool, od_server_state_t state, od_server_pool_cb_t callback, void **argv) { od_list_t *target = NULL; switch (state) { case OD_SERVER_IDLE: target = &pool->idle; break; case OD_SERVER_ACTIVE: target = &pool->active; break; case OD_SERVER_UNDEF: od_assert(0); break; } od_server_t *server; od_list_t *i, *n; od_list_foreach_safe (target, i, n) { server = od_container_of(i, od_server_t, link); int rc; rc = callback(server, argv); if (rc) { return server; } } return NULL; } static inline int od_server_pool_idle(od_server_pool_t *pool) { return pool->count_idle; } static inline int od_server_pool_active(od_server_pool_t *pool) { return pool->count_active; } static inline int od_server_pool_total(od_server_pool_t *pool) { return pool->count_active + pool->count_idle; } odyssey-1.6.0-rc1/sources/include/setproctitle.h000066400000000000000000000003001526064202200216700ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #define OD_MAX_PROC_TITLE_LEN 256 od_retcode_t od_setproctitlef(char **argv_ptr, int argv_len, char *fmt, ...); odyssey-1.6.0-rc1/sources/include/shared_pool.h000066400000000000000000000007551526064202200214640ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include struct od_shared_pool { od_multi_pool_t *mpool; mm_spinlock_t lock; char *name; od_list_t link; int pool_size; /* TODO: use full od_rule_pool_t here? */ int refs; }; od_shared_pool_t *od_shared_pool_create(const char *name); od_shared_pool_t *od_shared_pool_ref(od_shared_pool_t *sp); void od_shared_pool_unref(od_shared_pool_t *sp); odyssey-1.6.0-rc1/sources/include/sighandler.h000066400000000000000000000002631526064202200212770ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include void od_system_signal_handler(void *arg); odyssey-1.6.0-rc1/sources/include/soft_oom.h000066400000000000000000000011561526064202200210060ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include struct od_soft_oom_checker { od_config_soft_oom_t *config; int64_t machine_id; machine_wait_flag_t *stop_flag; atomic_uint_fast64_t current_memory_usage; }; /* starts at separated thread because it uses file i/o */ int od_soft_oom_start_checker(od_config_soft_oom_t *config, od_soft_oom_checker_t *checker); void od_soft_oom_stop_checker(od_soft_oom_checker_t *checker); int od_soft_oom_is_in_soft_oom(od_soft_oom_checker_t *checker, uint64_t *used_memory); odyssey-1.6.0-rc1/sources/include/sql/000077500000000000000000000000001526064202200176045ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/sql/minimal/000077500000000000000000000000001526064202200212325ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/sql/minimal/ast.h000066400000000000000000000045201526064202200221730ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Minimal SQL parser AST definitions. */ #include #include typedef enum { OD_SQL_MINIMAL_NODE_TYPE_INVALID = 0, OD_SQL_MINIMAL_NODE_TYPE_SHOW_STMT, OD_SQL_MINIMAL_NODE_TYPE_SET_STMT, OD_SQL_MINIMAL_NODE_TYPE_BEGIN_STMT, OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT, OD_SQL_MINIMAL_NODE_TYPE_DISCARD_STMT, OD_SQL_MINIMAL_NODE_TYPE_UNLISTEN_STMT, } od_sql_minimal_node_tag_t; typedef struct od_sql_minimal_node { od_sql_minimal_node_tag_t type; } od_sql_minimal_node_t; typedef struct { od_sql_minimal_node_tag_t type; char *name; } od_sql_minimal_show_stmt_t; typedef struct { od_sql_minimal_node_tag_t type; char *key; char *value; } od_sql_minimal_set_stmt_t; typedef struct { od_sql_minimal_node_tag_t type; } od_sql_minimal_begin_stmt_t; typedef struct { od_sql_minimal_node_tag_t type; char *name; int is_all; } od_sql_minimal_deallocate_stmt_t; typedef enum { OD_SQL_MINIMAL_DISCARD_ALL, OD_SQL_MINIMAL_DISCARD_TEMP, OD_SQL_MINIMAL_DISCARD_PLANS, OD_SQL_MINIMAL_DISCARD_SEQUENCES, } od_sql_minimal_discard_target_t; typedef struct { od_sql_minimal_node_tag_t type; od_sql_minimal_discard_target_t target; } od_sql_minimal_discard_stmt_t; typedef struct { od_sql_minimal_node_tag_t type; char *name; int is_all; } od_sql_minimal_unlisten_stmt_t; #define OD_PG_NAMEDATALEN 64 typedef struct { int parse_error; int is_discard_all; int is_unlisten_all; int is_deallocate_all; int has_deallocate_name; char deallocate_name[OD_PG_NAMEDATALEN]; } od_query_ctx_t; od_sql_minimal_node_t *od_sql_minimal_node_alloc(od_linear_alloc_t *al, od_sql_minimal_node_tag_t type, size_t size); void od_sql_minimal_node_free(od_sql_minimal_node_t *node); int od_sql_minimal_node_print(const od_sql_minimal_node_t *node, char *buf, size_t buflen); /* * Extract query-context flags from a minimal-parser AST into ctx. * On a non-matching AST, all flags are set to 0. * deallocate_name (if has_deallocate_name) is stored inplace (no heap alloc). */ void od_sql_minimal_extract_query_ctx(const od_sql_minimal_node_t *ast, od_query_ctx_t *ctx); /* * Reset query context: zeroes the struct (no heap allocations to free). * Safe to call on a zeroed/stack od_query_ctx_t. */ void od_query_ctx_reset(od_query_ctx_t *ctx); odyssey-1.6.0-rc1/sources/include/sql/minimal/ctx.h000066400000000000000000000016551526064202200222100ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Minimal SQL parser context. */ #include #include #include #include typedef void (*od_sql_minimal_error_cb_t)(const char *msg, void *userdata); typedef struct od_sql_minimal_parse_ctx { /* input buffer -- kept for offset arithmetic in SET_YYLLOC */ const char *scanbuf; size_t scanbuflen; od_linear_alloc_t *arena; od_sql_minimal_node_t *result; od_sql_minimal_error_cb_t error_cb; void *error_cb_userdata; int had_error; /* * used to recover from flex YY_FATAL_ERROR * (which calls exit() by default) */ jmp_buf fatal_jmp; int fatal_set; } od_sql_minimal_parse_ctx_t; void od_sql_minimal_parse_ctx_init(od_sql_minimal_parse_ctx_t *ctx, const char *input, size_t input_len, od_linear_alloc_t *arena, od_sql_minimal_error_cb_t error_cb, void *userdata); odyssey-1.6.0-rc1/sources/include/sql/minimal/keywords.h000066400000000000000000000002441526064202200232520ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int od_sql_minimal_keyword_lookup(const char *str, size_t len); odyssey-1.6.0-rc1/sources/include/sql/minimal/parser.h000066400000000000000000000006631526064202200227040ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include /* * returns the AST root on success, NULL on parse error. */ od_sql_minimal_node_t *od_sql_minimal_parse(const char *input, size_t input_len, od_linear_alloc_t *arena, od_sql_minimal_error_cb_t error_cb, void *userdata); odyssey-1.6.0-rc1/sources/include/stat.h000066400000000000000000000216141526064202200201350ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #define QUANTILES_WINDOW 2 #define QUANTILES_COMPRESSION 100 typedef struct od_stat_state od_stat_state_t; typedef struct od_stat od_stat_t; struct od_stat_state { uint64_t query_time_start; uint64_t tx_time_start; }; struct od_stat { bool enable_quantiles; uint8_t current_tdigest; atomic_uint_fast64_t count_query; atomic_uint_fast64_t count_tx; atomic_uint_fast64_t query_time; atomic_uint_fast64_t tx_time; atomic_uint_fast64_t count_wait; atomic_uint_fast64_t wait_time_us; atomic_uint_fast64_t recv_server; atomic_uint_fast64_t recv_client; atomic_uint_fast64_t count_parse; atomic_uint_fast64_t count_parse_reuse; atomic_uint_fast64_t count_cancel; td_histogram_t *transaction_hgram[QUANTILES_WINDOW]; td_histogram_t *query_hgram[QUANTILES_WINDOW]; }; static inline void od_stat_state_init(od_stat_state_t *state) { memset(state, 0, sizeof(*state)); } static inline void od_stat_init(od_stat_t *stat) { memset(stat, 0, sizeof(*stat)); } static inline void od_stat_free(od_stat_t *stat) { for (size_t i = 0; i < QUANTILES_WINDOW; ++i) { td_free(stat->transaction_hgram[i]); td_free(stat->query_hgram[i]); } } static inline void od_stat_query_start(od_stat_state_t *state) { if (!state->query_time_start) { state->query_time_start = machine_time_us(); } if (!state->tx_time_start) { state->tx_time_start = machine_time_us(); } } static inline void od_stat_wait_time(od_stat_t *stat, uint64_t wait_time_us) { atomic_fetch_add_explicit(&stat->wait_time_us, wait_time_us, memory_order_relaxed); atomic_fetch_add_explicit(&stat->count_wait, 1, memory_order_relaxed); } static inline void od_stat_parse(od_stat_t *stat) { atomic_fetch_add_explicit(&stat->count_parse, 1, memory_order_relaxed); } static inline void od_stat_parse_reuse(od_stat_t *stat) { atomic_fetch_add_explicit(&stat->count_parse_reuse, 1, memory_order_relaxed); } static inline void od_stat_cancel(od_stat_t *stat) { atomic_fetch_add_explicit(&stat->count_cancel, 1, memory_order_relaxed); } static inline void od_stat_query_end(od_stat_t *stat, od_stat_state_t *state, int in_transaction, int64_t *query_time) { int64_t diff; if (state->query_time_start) { diff = machine_time_us() - state->query_time_start; if (diff > 0) { *query_time = diff; atomic_fetch_add_explicit(&stat->query_time, diff, memory_order_relaxed); atomic_fetch_add_explicit(&stat->count_query, 1, memory_order_relaxed); if (stat->enable_quantiles) { td_add(stat->query_hgram[stat->current_tdigest], diff, 1); } } state->query_time_start = 0; } if (in_transaction) { return; } if (state->tx_time_start) { diff = machine_time_us() - state->tx_time_start; if (diff > 0) { atomic_fetch_add_explicit(&stat->tx_time, diff, memory_order_relaxed); atomic_fetch_add_explicit(&stat->count_tx, 1, memory_order_relaxed); if (stat->enable_quantiles) { td_add(stat->transaction_hgram [stat->current_tdigest], diff, 1); } } state->tx_time_start = 0; } } static inline void od_stat_recv_server(od_stat_t *stat, uint64_t bytes) { atomic_fetch_add_explicit(&stat->recv_server, bytes, memory_order_relaxed); } static inline void od_stat_recv_client(od_stat_t *stat, uint64_t bytes) { atomic_fetch_add_explicit(&stat->recv_client, bytes, memory_order_relaxed); } static inline void od_stat_copy(od_stat_t *dst, od_stat_t *src) { dst->count_query = atomic_load_explicit(&src->count_query, memory_order_relaxed); dst->count_tx = atomic_load_explicit(&src->count_tx, memory_order_relaxed); dst->query_time = atomic_load_explicit(&src->query_time, memory_order_relaxed); dst->tx_time = atomic_load_explicit(&src->tx_time, memory_order_relaxed); dst->count_wait = atomic_load_explicit(&src->count_wait, memory_order_relaxed); dst->wait_time_us = atomic_load_explicit(&src->wait_time_us, memory_order_relaxed); dst->recv_client = atomic_load_explicit(&src->recv_client, memory_order_relaxed); dst->recv_server = atomic_load_explicit(&src->recv_server, memory_order_relaxed); dst->count_parse = atomic_load_explicit(&src->count_parse, memory_order_relaxed); dst->count_parse_reuse = atomic_load_explicit(&src->count_parse_reuse, memory_order_relaxed); dst->count_cancel = atomic_load_explicit(&src->count_cancel, memory_order_relaxed); } static inline void od_stat_sum(od_stat_t *sum, od_stat_t *stat) { sum->count_query += atomic_load_explicit(&stat->count_query, memory_order_relaxed); sum->count_tx += atomic_load_explicit(&stat->count_tx, memory_order_relaxed); sum->query_time += atomic_load_explicit(&stat->query_time, memory_order_relaxed); sum->tx_time += atomic_load_explicit(&stat->tx_time, memory_order_relaxed); sum->count_wait += atomic_load_explicit(&stat->count_wait, memory_order_relaxed); sum->wait_time_us += atomic_load_explicit(&stat->wait_time_us, memory_order_relaxed); sum->recv_client += atomic_load_explicit(&stat->recv_client, memory_order_relaxed); sum->recv_server += atomic_load_explicit(&stat->recv_server, memory_order_relaxed); sum->count_parse += atomic_load_explicit(&stat->count_parse, memory_order_relaxed); sum->count_parse_reuse += atomic_load_explicit(&stat->count_parse_reuse, memory_order_relaxed); sum->count_cancel += atomic_load_explicit(&stat->count_cancel, memory_order_relaxed); } static inline void od_stat_update_of(atomic_uint_fast64_t *prev, atomic_uint_fast64_t *current) { atomic_store_explicit( prev, atomic_load_explicit(current, memory_order_relaxed), memory_order_relaxed); } static inline void od_stat_update(od_stat_t *dst, od_stat_t *stat) { od_stat_update_of(&dst->count_query, &stat->count_query); od_stat_update_of(&dst->count_tx, &stat->count_tx); od_stat_update_of(&dst->query_time, &stat->query_time); od_stat_update_of(&dst->tx_time, &stat->tx_time); od_stat_update_of(&dst->count_wait, &stat->count_wait); od_stat_update_of(&dst->wait_time_us, &stat->wait_time_us); od_stat_update_of(&dst->recv_client, &stat->recv_client); od_stat_update_of(&dst->recv_server, &stat->recv_server); od_stat_update_of(&dst->count_parse, &stat->count_parse); od_stat_update_of(&dst->count_parse_reuse, &stat->count_parse_reuse); od_stat_update_of(&dst->count_cancel, &stat->count_cancel); } static inline void od_stat_average(od_stat_t *avg, od_stat_t *current, od_stat_t *prev, uint64_t prev_time_us) { const uint64_t interval_usec = 1000000; uint64_t interval_us; interval_us = machine_time_us() - prev_time_us; if (interval_us <= 0) { return; } uint64_t count_query; count_query = atomic_load_explicit(¤t->count_query, memory_order_relaxed) - atomic_load_explicit(&prev->count_query, memory_order_relaxed); uint64_t count_tx; count_tx = atomic_load_explicit(¤t->count_tx, memory_order_relaxed) - atomic_load_explicit(&prev->count_tx, memory_order_relaxed); uint64_t count_wait; count_wait = atomic_load_explicit(¤t->count_wait, memory_order_relaxed) - atomic_load_explicit(&prev->count_wait, memory_order_relaxed); uint64_t count_parse; count_parse = atomic_load_explicit(¤t->count_parse, memory_order_relaxed) - atomic_load_explicit(&prev->count_parse, memory_order_relaxed); uint64_t count_parse_reuse; count_parse_reuse = atomic_load_explicit(¤t->count_parse_reuse, memory_order_relaxed) - atomic_load_explicit(&prev->count_parse_reuse, memory_order_relaxed); avg->count_query = (count_query * interval_usec) / interval_us; avg->count_tx = (count_tx * interval_usec) / interval_us; avg->count_parse = (count_parse * interval_usec) / interval_us; avg->count_parse_reuse = (count_parse_reuse * interval_usec) / interval_us; if (count_query > 0) { avg->query_time = (atomic_load_explicit(¤t->query_time, memory_order_relaxed) - atomic_load_explicit(&prev->query_time, memory_order_relaxed)) / count_query; } if (count_tx > 0) { avg->tx_time = (atomic_load_explicit(¤t->tx_time, memory_order_relaxed) - atomic_load_explicit(&prev->tx_time, memory_order_relaxed)) / count_tx; } if (count_wait > 0) { avg->wait_time_us = (atomic_load_explicit(¤t->wait_time_us, memory_order_relaxed) - atomic_load_explicit(&prev->wait_time_us, memory_order_relaxed)) / count_wait; } avg->recv_client = ((atomic_load_explicit(¤t->recv_client, memory_order_relaxed) - atomic_load_explicit(&prev->recv_client, memory_order_relaxed)) * interval_usec) / interval_us; avg->recv_server = ((atomic_load_explicit(¤t->recv_server, memory_order_relaxed) - atomic_load_explicit(&prev->recv_server, memory_order_relaxed)) * interval_usec) / interval_us; } odyssey-1.6.0-rc1/sources/include/status.h000066400000000000000000000125071526064202200205060ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef enum { OD_UNDEF, OD_OK, OD_SKIP, OD_REPLACED, OD_ATTACH, OD_WAIT_SYNC, OD_READ_FULL, OD_STOP, OD_COPY_IN_RECEIVED, OD_READAHEAD_IS_FULL, OD_ASYNC_MSG_AVAILABLE, OD_EOOM, OD_EATTACH, OD_EATTACH_TOO_MANY_CONNECTIONS, OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH, OD_ESERVER_CONNECT, OD_ESERVER_READ, OD_ESERVER_WRITE, OD_ESERVER_READAHEAD_FULL, OD_ESERVER_OOM, OD_ECLIENT_READ, OD_ECLIENT_PROTOCOL_ERROR, OD_ECLIENT_COPY_IN_XPROTO, OD_ECLIENT_WRITE, OD_ECLIENT_READAHEAD_FULL, OD_ECLIENT_TIMEOUT, OD_ECLIENT_KILLED, OD_ESYNC_BROKEN, OD_ECATCHUP_TIMEOUT, OD_EGRACEFUL_SHUTDOWN, OD_EIDLE_TIMEOUT, OD_EIDLE_IN_TRANSACTION_TIMEOUT } od_frontend_status_t; static inline char *od_frontend_status_to_str(od_frontend_status_t status) { switch (status) { case OD_UNDEF: return "OD_UNDEF"; case OD_OK: return "OD_OK"; case OD_SKIP: return "OD_SKIP"; case OD_REPLACED: return "OD_REPLACED"; case OD_ATTACH: return "OD_ATTACH"; case OD_WAIT_SYNC: return "OD_WAIT_SYNC"; case OD_COPY_IN_RECEIVED: return "OD_COPY_IN_RECEIVED"; case OD_STOP: return "OD_STOP"; case OD_EOOM: return "OD_EOOM"; case OD_READ_FULL: return "OD_READ_FULL"; case OD_ASYNC_MSG_AVAILABLE: return "OD_ASYNC_MSG_AVAILABLE"; case OD_EATTACH: return "OD_EATTACH"; case OD_EATTACH_TOO_MANY_CONNECTIONS: return "OD_EATTACH_TOO_MANY_CONNECTIONS"; case OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH: return "OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH"; case OD_ESERVER_CONNECT: return "OD_ESERVER_CONNECT"; case OD_ESERVER_READ: return "OD_ESERVER_READ"; case OD_ESERVER_WRITE: return "OD_ESERVER_WRITE"; case OD_ESERVER_READAHEAD_FULL: return "OD_ESERVER_READAHEAD_FULL"; case OD_ESERVER_OOM: return "OD_ESERVER_OOM"; case OD_ECLIENT_READ: return "OD_ECLIENT_READ"; case OD_ECLIENT_PROTOCOL_ERROR: return "OD_ECLIENT_PROTOCOL_ERROR"; case OD_ECLIENT_COPY_IN_XPROTO: return "OD_ECLIENT_COPY_IN_XPROTO"; case OD_ECLIENT_WRITE: return "OD_ECLIENT_WRITE"; case OD_ECLIENT_READAHEAD_FULL: return "OD_ECLIENT_READAHEAD_FULL"; case OD_ECLIENT_KILLED: return "OD_ECLIENT_KILLED"; case OD_ECLIENT_TIMEOUT: return "OD_ECLIENT_TIMEOUT"; case OD_ESYNC_BROKEN: return "OD_ESYNC_BROKEN"; case OD_ECATCHUP_TIMEOUT: return "OD_ECATCHUP_TIMEOUT"; case OD_READAHEAD_IS_FULL: return "OD_EREADAHEAD_IS_FULL"; case OD_EGRACEFUL_SHUTDOWN: return "OD_EGRACEFUL_SHUTDOWN"; case OD_EIDLE_TIMEOUT: return "OD_EIDLE_TIMEOUT"; case OD_EIDLE_IN_TRANSACTION_TIMEOUT: return "OD_EIDLE_IN_TRANSACTION_TIMEOUT"; } return "UNKNOWN"; } static const od_frontend_status_t od_frontend_status_errs[] = { OD_EOOM, OD_EATTACH, OD_EATTACH_TOO_MANY_CONNECTIONS, OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH, OD_ESERVER_CONNECT, OD_ESERVER_READ, OD_ESERVER_WRITE, OD_ESERVER_READAHEAD_FULL, OD_ESERVER_OOM, OD_ECLIENT_WRITE, OD_ECLIENT_READAHEAD_FULL, OD_ECLIENT_KILLED, OD_ECLIENT_TIMEOUT, OD_ECLIENT_READ, OD_ECLIENT_PROTOCOL_ERROR, OD_ECLIENT_COPY_IN_XPROTO, OD_ESYNC_BROKEN, OD_ECATCHUP_TIMEOUT, OD_EGRACEFUL_SHUTDOWN, OD_EIDLE_TIMEOUT, OD_EIDLE_IN_TRANSACTION_TIMEOUT }; #define OD_FRONTEND_STATUS_ERRORS_TYPES_COUNT \ sizeof(od_frontend_status_errs) / sizeof(od_frontend_status_errs[0]) static inline bool od_frontend_status_is_err(od_frontend_status_t status) { for (size_t i = 0; i < OD_FRONTEND_STATUS_ERRORS_TYPES_COUNT; ++i) { if (od_frontend_status_errs[i] == status) { return true; } } return false; } typedef enum { OD_ROUTER_OK, OD_ROUTER_ERROR, OD_ROUTER_ERROR_NOT_FOUND, OD_ROUTER_ERROR_LIMIT, OD_ROUTER_ERROR_LIMIT_ROUTE, OD_ROUTER_ERROR_TIMEDOUT, OD_ROUTER_ERROR_REPLICATION, OD_ROUTER_CLIENT_DISCONNECTED, OD_ROUTER_INSUFFICIENT_ACCESS, OD_ROUTER_NEED_WAIT, } od_router_status_t; static inline char *od_router_status_to_str(od_router_status_t status) { switch (status) { case OD_ROUTER_OK: return "OD_ROUTER_OK"; case OD_ROUTER_ERROR: return "OD_ROUTER_ERROR"; case OD_ROUTER_ERROR_NOT_FOUND: return "OD_ROUTER_ERROR_NOT_FOUND"; case OD_ROUTER_ERROR_LIMIT: return "OD_ROUTER_ERROR_LIMIT"; case OD_ROUTER_CLIENT_DISCONNECTED: return "OD_ROUTER_CLIENT_DISCONNECTED"; case OD_ROUTER_ERROR_LIMIT_ROUTE: return "OD_ROUTER_ERROR_LIMIT_ROUTE"; case OD_ROUTER_ERROR_TIMEDOUT: return "OD_ROUTER_ERROR_TIMEDOUT"; case OD_ROUTER_ERROR_REPLICATION: return "OD_ROUTER_ERROR_REPLICATION"; case OD_ROUTER_NEED_WAIT: return "OD_ROUTER_NEED_WAIT"; default: return "unkonown"; } } static const od_router_status_t od_router_status_errs[] = { OD_ROUTER_ERROR, OD_ROUTER_ERROR_NOT_FOUND, OD_ROUTER_ERROR_LIMIT, OD_ROUTER_ERROR_LIMIT_ROUTE, OD_ROUTER_ERROR_TIMEDOUT, OD_ROUTER_ERROR_REPLICATION, OD_ROUTER_CLIENT_DISCONNECTED }; #define OD_ROUTER_STATUS_ERRORS_TYPES_COUNT \ sizeof(od_router_status_errs) / sizeof(od_router_status_errs[0]) /* errors that could be counted per route */ static const od_router_status_t od_router_route_status_errs[] = { OD_ROUTER_ERROR_LIMIT_ROUTE, }; #define OD_ROUTER_ROUTE_STATUS_ERRORS_TYPES_COUNT \ sizeof(od_router_route_status_errs) / \ sizeof(od_router_route_status_errs[0]) static inline bool od_router_status_is_err(od_router_status_t status) { for (size_t i = 0; i < OD_ROUTER_STATUS_ERRORS_TYPES_COUNT; ++i) { if (od_router_status_errs[i] == status) { return true; } } return false; } odyssey-1.6.0-rc1/sources/include/storage.h000066400000000000000000000052401526064202200206230ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include /* Storage Watchdog */ typedef enum { OD_RULE_STORAGE_REMOTE, OD_RULE_STORAGE_LOCAL, } od_rule_storage_type_t; struct od_storage_watchdog { char *route_usr; char *route_db; char *storage_user; char *storage_db; char *query; int interval; od_global_t *global; od_rule_storage_t *storage; machine_wait_flag_t *online; }; od_storage_watchdog_t *od_storage_watchdog_allocate(od_global_t *); int od_storage_watchdog_free(od_storage_watchdog_t *watchdog); typedef struct { uint64_t last_update_time_ms; mm_spinlock_t values_lock; int alive; bool is_read_write; int64_t repl_lag_sec; } od_storage_endpoint_status_t; void od_storage_endpoint_status_init(od_storage_endpoint_status_t *status); void od_storage_endpoint_status_destroy(od_storage_endpoint_status_t *status); bool od_storage_endpoint_status_is_outdated( od_storage_endpoint_status_t *status, uint64_t recheck_interval); void od_storage_endpoint_status_get(od_storage_endpoint_status_t *status, od_storage_endpoint_status_t *out); void od_storage_endpoint_status_set(od_storage_endpoint_status_t *status, const od_storage_endpoint_status_t *value); void od_storage_endpoint_status_set_dead(od_storage_endpoint_status_t *status); struct od_storage_endpoint { od_address_t address; od_storage_endpoint_status_t status; struct od_rule_storage *storage; }; int od_storage_parse_endpoints(const char *host_str, od_storage_endpoint_t **out, size_t *count); struct od_rule_storage { od_tls_opts_t *tls_opts; char *name; char *type; od_rule_storage_type_t storage_type; od_storage_endpoint_t *endpoints; size_t endpoints_count; char *host; /* host or host,host or [host]:port[,host...] */ int port; /* default port */ int server_max_routing; od_storage_watchdog_t *watchdog; int64_t watchdog_coro_id; od_list_t link; int endpoints_status_poll_interval_ms; od_storage_balancing_t balancing; atomic_int_fast64_t refs; int mark; /* transient flag used during storage merge */ }; /* storage API */ od_rule_storage_t *od_rules_storage_allocate(void); od_rule_storage_t *od_rules_storage_ref(od_rule_storage_t *s); void od_rules_storage_unref(od_rule_storage_t *); od_storage_endpoint_t *od_storage_find_endpoint(od_rule_storage_t *storage, const od_address_t *address); /* watchdog */ void od_storage_watchdog_watch(void *arg); int od_storage_watchdog_set_offline(od_storage_watchdog_t *watchdog); odyssey-1.6.0-rc1/sources/include/stream.h000066400000000000000000000020731526064202200204530ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Streaming subsytem mo make some parts of stream more efficient * by gathering several io operations into one */ #include #include #include od_frontend_status_t od_stream_server_until_rfq(char *ctx, od_server_t *server, uint32_t timeout_ms); od_frontend_status_t od_stream_copy_to_server(char *ctx, od_client_t *client, od_server_t *server, machine_msg_t *additional, uint32_t timeout_ms); od_frontend_status_t od_service_stream_server_until_rfq(char *ctx, od_server_t *server, int ignore_errors, uint32_t timeout_ms); od_frontend_status_t od_stream_server_sync(char *ctx, od_server_t *server, uint32_t timeout_ms); typedef od_frontend_status_t (*od_stream_on_responce_cb)(kiwi_be_type_t type, void *arg); od_frontend_status_t od_stream_server_exact_completes(char *ctx, od_server_t *server, int n, od_stream_on_responce_cb on_response, void *arg, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/system.h000066400000000000000000000023011526064202200204760ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include struct od_system_server { mm_io_t *io; mm_eventfd_t shutdown_efd; machine_tls_t *tls; /* tls handles replaced by a reload, still referenced by live clients */ mm_vector_t tls_retired; od_config_listen_t *config; mm_addrinfo_t *addr; od_global_t *global; od_list_t link; od_id_t sid; atomic_bool closed; int64_t coro_id; atomic_uint_fast64_t clients; }; void od_system_server_free(od_system_server_t *server); od_system_server_t *od_system_server_init(void); void od_system_server_shutdown(od_system_server_t *server); struct od_system { int64_t machine; int64_t sighandler_machine; machine_channel_t *system_channel; od_global_t *global; }; od_system_t *od_system_create(void); void od_system_init(od_system_t *); int od_system_start(od_system_t *, od_global_t *); void od_system_config_reload(od_system_t *); void od_system_free(od_system_t *system); int od_system_send_msg(od_system_t *system, int msg_type, void *arg); odyssey-1.6.0-rc1/sources/include/sysv.h000066400000000000000000000003171526064202200201630ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ static inline size_t od_get_ncpu(void) { #ifdef _SC_NPROCESSORS_ONLN return sysconf(_SC_NPROCESSORS_ONLN); #endif return 1; } odyssey-1.6.0-rc1/sources/include/tdigest.h000066400000000000000000000044051526064202200206240ustar00rootroot00000000000000#pragma once /*////////////////////////////////////////////////////////////////////////////// */ /* tdigest */ /* */ /* Copyright (c) 2018 Andrew Werner, All rights reserved. */ /* */ /* tdigest is an implementation of Ted Dunning's streaming quantile estimation */ /* data structure. */ /* This implementation is intended to be like the new MergingHistogram. */ /* It focuses on being in portable C that should be easy to integrate into other */ /* languages. In particular it provides mechanisms to preallocate all memory */ /* at construction time. */ /* */ /* The implementation is a direct descendent of */ /* https://github.com/tdunning/t-digest/ */ /* */ /* TODO: add a Ted Dunning Copyright notice. */ /* */ /*////////////////////////////////////////////////////////////////////////////// */ typedef struct td_histogram td_histogram_t; /* td_new allocates a new histogram. */ /* It is similar to init but assumes that it can use malloc. */ td_histogram_t *td_new(double compression); /* clear histogram in-place */ void td_safe_free(td_histogram_t *h); /* copy histogram to another memory */ void td_copy(td_histogram_t *dst, td_histogram_t *src); /* td_free frees the memory associated with h. */ void td_free(td_histogram_t *h); /* td_add adds val to h with the specified count. */ void td_add(td_histogram_t *h, double val, double count); /* td_merge merges the data from from into into. */ void td_merge(td_histogram_t *into, td_histogram_t *from); /* td_reset resets a histogram. */ void td_reset(td_histogram_t *h); /* td_value_at queries h for the value at q. */ /* If q is not in [0, 1], NAN will be returned. */ double td_value_at(td_histogram_t *h, double q); /* td_value_at queries h for the quantile of val. */ /* The returned value will be in [0, 1]. */ double td_quantile_of(td_histogram_t *h, double val); /* td_trimmed_mean returns the mean of data from the lo quantile to the */ /* hi quantile. */ double td_trimmed_mean(td_histogram_t *h, double lo, double hi); /* td_total_count returns the total count contained in h. */ double td_total_count(td_histogram_t *h); /* td_total_sum returns the sum of all the data added to h. */ double td_total_sum(td_histogram_t *h); /* td_decay multiplies all counters by factor. */ void td_decay(td_histogram_t *h, double factor); odyssey-1.6.0-rc1/sources/include/tests/000077500000000000000000000000001526064202200201475ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/tests/odyssey_test.h000066400000000000000000000063141526064202200230620ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include /* for hba tests */ #include #include extern char test_substring[1024]; /* This type of test will run in two cases: 1. No test substring is provided to the odyssey_test binary. 2. A test substring is provided, and the function's name contains this substring. */ #define odyssey_test(function) \ do { \ if (strlen(test_substring) != 0 && \ strcasestr(#function, test_substring) == NULL) { \ break; \ } \ struct timeval tv_start, tv_stop; \ fprintf(stdout, "%s: ", #function); \ fflush(stdout); \ gettimeofday(&tv_start, NULL); \ (function)(); \ gettimeofday(&tv_stop, NULL); \ fprintf(stdout, "ok (%ld ms)\n", \ (tv_stop.tv_sec - tv_start.tv_sec) * 1000 + \ (tv_stop.tv_usec - tv_start.tv_usec) / 1000); \ } while (0); /* This test type is analogous to odyssey_test, but will not run if a test substring isn't provided. It is not intended to run in CI, but rather serves as a playground for manual checking and experimenting. */ #define odyssey_playground_test(function) \ do { \ if (strlen(test_substring) == 0) { \ break; \ } \ odyssey_test(function); \ } while (0); #define test(expression) \ do { \ if (!(expression)) { \ fprintf(stdout, \ "fail (%s:%d) with error \"%s\" (%d) %s\n", \ __FILE__, __LINE__, strerror(errno), errno, \ #expression); \ fflush(stdout); \ abort(); \ } \ } while (0); static inline void odyssey_test_set_test_substring(const char *substring) { test(strlen(substring) < (int)sizeof(test_substring) - 1); strcpy(test_substring, substring); } typedef struct { struct timespec start; struct timespec end; } benchmark_timer_t; static inline void timer_start(benchmark_timer_t *timer) { clock_gettime(CLOCK_MONOTONIC, &timer->start); } static inline double timer_end(benchmark_timer_t *timer) { clock_gettime(CLOCK_MONOTONIC, &timer->end); double start_sec = timer->start.tv_sec + timer->start.tv_nsec / 1e9; double end_sec = timer->end.tv_sec + timer->end.tv_nsec / 1e9; return end_sec - start_sec; } odyssey-1.6.0-rc1/sources/include/thread_global.h000066400000000000000000000006771526064202200217570ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef struct { od_conn_eject_info *info; int wid; /* worker id */ /* TODO: store here some metainfo about incoming connections flow and use in somehow */ } od_thread_global; extern od_retcode_t od_thread_global_init(od_thread_global **gl); extern od_thread_global **od_thread_global_get(void); extern void od_thread_global_free(void *gl); odyssey-1.6.0-rc1/sources/include/thread_pool.h000066400000000000000000000033561526064202200214650ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * thread_pool.h: yet another thread pool for blocking tasks execution */ #include #include #include #include #include #include typedef void (*od_thread_pool_result_dtor_t)(void *result); typedef struct { atomic_uintptr_t value; od_thread_pool_result_dtor_t value_dtor; atomic_uint_fast64_t refs; mm_wait_flag_t *wait; } od_future_t; od_future_t *od_future_create(od_thread_pool_result_dtor_t dtor); void od_future_ref(od_future_t *future); void od_future_unref(od_future_t *future); void *od_future_get_result(od_future_t *future); typedef void *(*od_thread_pool_task_fn_t)(void *arg); typedef void (*od_thread_pool_task_arg_dtor_t)(void *arg); typedef struct { od_thread_pool_task_fn_t fn; void *arg; od_thread_pool_task_arg_dtor_t arg_dtor; od_future_t *future; int detached; } od_thread_pool_task_t; typedef struct { char name[64]; int64_t machine_id; size_t idx; od_thread_pool_t *pool; } od_thread_pool_worker_t; struct od_thread_pool { char name[64]; size_t size; od_thread_pool_worker_t *workers; mm_queue_t queue; mm_wait_list_t notifier; atomic_uint_fast64_t version; atomic_uint_fast64_t stop; }; int od_thread_pool_init(od_thread_pool_t *pool, const char *name, size_t size, size_t queue_size); void od_thread_pool_destroy(od_thread_pool_t *pool); od_future_t *od_thread_pool_submit(od_thread_pool_t *pool, od_thread_pool_task_fn_t fn, void *arg, od_thread_pool_task_arg_dtor_t arg_dtor, od_thread_pool_result_dtor_t result_dtor, int detach); int od_thread_pool_wait(od_future_t *future, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/include/tls.h000066400000000000000000000007601526064202200177630ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include machine_tls_t *od_tls_frontend(od_config_listen_t *); od_retcode_t od_tls_frontend_validate(od_config_listen_t *, char *, int); int od_tls_frontend_accept(od_client_t *, od_logger_t *, od_config_listen_t *, machine_tls_t *); machine_tls_t *od_tls_backend(od_tls_opts_t *); int od_tls_backend_connect(od_server_t *, od_logger_t *, od_tls_opts_t *); odyssey-1.6.0-rc1/sources/include/tls_config.h000066400000000000000000000020311526064202200213010ustar00rootroot00000000000000 #pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef enum { /* do not change the order */ OD_CONFIG_TLS_DISABLE, OD_CONFIG_TLS_ALLOW, OD_CONFIG_TLS_PREFER, OD_CONFIG_TLS_REQUIRE, OD_CONFIG_TLS_VERIFY_CA, OD_CONFIG_TLS_VERIFY_FULL } od_config_tls_t; static inline char *od_config_tls_to_str(od_config_tls_t tls) { switch (tls) { case OD_CONFIG_TLS_DISABLE: return "disable"; case OD_CONFIG_TLS_ALLOW: return "allow"; case OD_CONFIG_TLS_PREFER: return "prefer"; case OD_CONFIG_TLS_REQUIRE: return "require"; case OD_CONFIG_TLS_VERIFY_CA: return "verify_ca"; case OD_CONFIG_TLS_VERIFY_FULL: return "verify_full"; } return "UNKNOWN"; } struct od_tls_opts { od_config_tls_t tls_mode; char *tls; char *tls_ca_file; char *tls_key_file; char *tls_cert_file; char *tls_protocols; }; typedef struct od_tls_opts od_tls_opts_t; od_tls_opts_t *od_tls_opts_alloc(void); od_retcode_t od_tls_opts_free(od_tls_opts_t *); int od_tls_opts_files_eq(const od_tls_opts_t *, const od_tls_opts_t *); odyssey-1.6.0-rc1/sources/include/tsa.h000066400000000000000000000015521526064202200177500ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include typedef enum { OD_TARGET_SESSION_ATTRS_RW, OD_TARGET_SESSION_ATTRS_RO, OD_TARGET_SESSION_ATTRS_ANY, OD_TARGET_SESSION_ATTRS_UNDEF, OD_TARGET_SESSION_ATTRS_PREFER_STANDBY, } od_target_session_attrs_t; static inline char * od_target_session_attrs_to_str(od_target_session_attrs_t tsa) { switch (tsa) { case OD_TARGET_SESSION_ATTRS_RW: return "read-write"; case OD_TARGET_SESSION_ATTRS_RO: return "read-only"; case OD_TARGET_SESSION_ATTRS_ANY: return "any"; case OD_TARGET_SESSION_ATTRS_PREFER_STANDBY: return "prefer-standby"; case OD_TARGET_SESSION_ATTRS_UNDEF: return "no specified"; } return ""; } od_target_session_attrs_t od_tsa_get_effective(od_client_t *client); int od_tsa_match_rw_state(od_target_session_attrs_t attrs, int is_rw); odyssey-1.6.0-rc1/sources/include/types.h000066400000000000000000000036131526064202200203250ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* * Types definitions to use it in cycle references (like client <-> server or pool <-> server) */ typedef struct od_global od_global_t; typedef struct od_instance od_instance_t; typedef struct od_system_server od_system_server_t; typedef struct od_router od_router_t; typedef struct od_cron od_cron_t; typedef struct od_worker_pool od_worker_pool_t; typedef struct od_extension od_extension_t; typedef struct od_hba od_hba_t; typedef struct od_system od_system_t; typedef struct od_address od_address_t; typedef struct od_client od_client_t; typedef struct od_client_pool od_client_pool_t; typedef struct od_error_logger od_error_logger_t; typedef struct od_server od_server_t; typedef struct od_route od_route_t; typedef struct od_server_pool od_server_pool_t; typedef struct od_multi_pool_key od_multi_pool_key_t; typedef struct od_multi_pool_element od_multi_pool_element_t; typedef struct od_multi_pool od_multi_pool_t; typedef struct od_shared_pool od_shared_pool_t; typedef struct od_soft_oom_checker od_soft_oom_checker_t; typedef struct od_config_listen od_config_listen_t; typedef struct od_config_conn_drop_options od_config_conn_drop_options_t; typedef struct od_config od_config_t; typedef struct od_config_soft_oom od_config_soft_oom_t; typedef struct od_config_soft_oom_drop od_config_soft_oom_drop_t; typedef struct od_relay_xbuf od_relay_xbuf_t; typedef struct od_xplan od_xplan_t; typedef struct od_pstmt od_pstmt_t; typedef struct od_relay od_relay_t; typedef struct od_rule_auth od_rule_auth_t; typedef struct od_rule od_rule_t; typedef struct od_rules od_rules_t; typedef struct od_storage_endpoint od_storage_endpoint_t; typedef struct od_thread_pool od_thread_pool_t; typedef struct od_rule_storage od_rule_storage_t; typedef struct od_storage_watchdog od_storage_watchdog_t; typedef struct od_linear_alloc od_linear_alloc_t; odyssey-1.6.0-rc1/sources/include/util.h000066400000000000000000000100451526064202200201330ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include static inline int od_vsnprintf(char *buf, int size, const char *fmt, va_list args) __attribute__((format(printf, 3, 0))); static inline int od_vasprintf(char **__restrict bufp, const char *fmt, va_list args) __attribute__((format(printf, 2, 0))); static inline int od_asprintf(char **__restrict bufp, const char *fmt, ...) __attribute__((format(printf, 2, 3))); static inline int od_snprintf(char *buf, int size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); static inline int od_vsnprintf(char *buf, int size, const char *fmt, va_list args) { int rc; rc = vsnprintf(buf, size, fmt, args); if (od_unlikely(rc >= size)) { rc = (size - 1); } return rc; } static inline int od_vasprintf(char **__restrict bufp, const char *fmt, va_list args) { int rc = vasprintf(bufp, fmt, args); if (rc == -1) { *bufp = NULL; return NOT_OK_RESPONSE; } /* make it possible to od_free() the result */ char *t = od_strdup(*bufp); free(*bufp); *bufp = t; if (t == NULL) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline int od_asprintf(char **__restrict bufp, const char *fmt, ...) { va_list args; va_start(args, fmt); int rc = vasprintf(bufp, fmt, args); va_end(args); if (rc == -1) { *bufp = NULL; return NOT_OK_RESPONSE; } /* make it possible to od_free() the result */ char *t = od_strdup(*bufp); free(*bufp); *bufp = t; if (t == NULL) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline int od_snprintf(char *buf, int size, const char *fmt, ...) { va_list args; va_start(args, fmt); int rc; rc = od_vsnprintf(buf, size, fmt, args); va_end(args); return rc; } static inline int od_concat_prefer_right(char *out, size_t max, const char *left, size_t left_len, const char *right, size_t right_len) { if (max == 0) { return 0; } if (right_len >= max) { return od_snprintf(out, max, "%.*s", (int)(max - 1), right + right_len - (max - 1)); } size_t max_left = (max > 1 + right_len) ? (max - 1 - right_len) : 0; if (left_len > max_left) { left_len = max_left; } return od_snprintf(out, max, "%.*s%s", (int)left_len, left, right); } static inline char *od_strdup_from_buf(const char *source, size_t size) { char *str = od_malloc(size + 1); if (str == NULL) { return NULL; } memcpy(str, source, size); str[size] = '\0'; return str; } static inline long od_memtol(char *data, size_t data_size, char **end_ptr, int base) { /* Only 10 is supported */ if (base != 10) { abort(); } size_t i = 0; while (i < data_size && isspace(data[i])) { i++; } if (i >= data_size) { return 0; } char sign = data[i]; if (sign == '-' || sign == '+') { i++; } if (i >= data_size || !isdigit(data[i])) { return 0; } long result = 0; while (i < data_size && isdigit(data[i])) { result = result * 10 + (data[i] - '0'); i++; } if (i < data_size && !isspace(data[i])) { return 0; } if (end_ptr) { *end_ptr = data + i; } if (sign == '-') { return -result; } return result; } static inline uint32_t od_bswap32(uint32_t x) { return ((x << 24) & 0xff000000) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | ((x >> 24) & 0x000000ff); } static inline void od_array_reverse_p(void **arr, size_t start, size_t end) { while (start < end) { void *tmp = arr[start]; arr[start] = arr[end]; arr[end] = tmp; ++start; --end; } } static inline void od_array_rotate_left_p(void **arr, size_t n, size_t k) { if (n <= 1) { return; } k %= n; if (k == 0) { return; } od_array_reverse_p(arr, 0, k - 1); od_array_reverse_p(arr, k, n - 1); od_array_reverse_p(arr, 0, n - 1); } static inline void od_insertion_sort_p(void **arr, size_t n, int (*cmp)(void *arg, const void *a, const void *b), void *arg) { for (size_t i = 1; i < n; ++i) { void *key = arr[i]; size_t j = i; while (j > 0 && cmp(arg, arr[j - 1], key) > 0) { arr[j] = arr[j - 1]; --j; } arr[j] = key; } } odyssey-1.6.0-rc1/sources/include/utils/000077500000000000000000000000001526064202200201455ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/include/utils/ascii.h000066400000000000000000000043271526064202200214140ustar00rootroot00000000000000/*----------------------------------------------------------------------- * ascii.h * * Portions Copyright (c) 1999-2025, PostgreSQL Global Development Group * * src/include/utils/ascii.h * *----------------------------------------------------------------------- */ #ifndef _ASCII_H_ #define _ASCII_H_ #include "port/simd.h" extern void ascii_safe_strlcpy(char *dest, const char *src, size_t destsiz); /* * Verify a chunk of bytes for valid ASCII. * * Returns false if the input contains any zero bytes or bytes with the * high-bit set. Input len must be a multiple of the chunk size (8 or 16). */ static inline bool is_valid_ascii(const unsigned char *s, int len) { const unsigned char *const s_end = s + len; Vector8 chunk; Vector8 highbit_cum = vector8_broadcast(0); #ifdef USE_NO_SIMD Vector8 zero_cum = vector8_broadcast(0x80); #endif Assert(len % sizeof(chunk) == 0); while (s < s_end) { vector8_load(&chunk, s); /* Capture any zero bytes in this chunk. */ #ifdef USE_NO_SIMD /* * First, add 0x7f to each byte. This sets the high bit in each byte, * unless it was a zero. If any resulting high bits are zero, the * corresponding high bits in the zero accumulator will be cleared. * * If none of the bytes in the chunk had the high bit set, the max * value each byte can have after the addition is 0x7f + 0x7f = 0xfe, * and we don't need to worry about carrying over to the next byte. If * any input bytes did have the high bit set, it doesn't matter * because we check for those separately. */ zero_cum &= (chunk + vector8_broadcast(0x7F)); #else /* * Set all bits in each lane of the highbit accumulator where input * bytes are zero. */ highbit_cum = vector8_or( highbit_cum, vector8_eq(chunk, vector8_broadcast(0))); #endif /* Capture all set bits in this chunk. */ highbit_cum = vector8_or(highbit_cum, chunk); s += sizeof(chunk); } /* Check if any high bits in the high bit accumulator got set. */ if (vector8_is_highbit_set(highbit_cum)) { return false; } #ifdef USE_NO_SIMD /* Check if any high bits in the zero accumulator got cleared. */ if (zero_cum != vector8_broadcast(0x80)) { return false; } #endif return true; } #endif /* _ASCII_H_ */ odyssey-1.6.0-rc1/sources/include/worker.h000066400000000000000000000007671526064202200205010ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include typedef struct od_worker od_worker_t; struct od_worker { int64_t machine; int id; machine_channel_t *task_channel; uint64_t clients_processed; od_global_t *global; }; void od_worker_init(od_worker_t *, od_global_t *, int); int od_worker_start(od_worker_t *); void od_worker_shutdown(od_worker_t *); od_linear_alloc_t *od_worker_get_local_linear_alloc(void); odyssey-1.6.0-rc1/sources/include/worker_pool.h000066400000000000000000000035721526064202200215270ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include struct od_worker_pool { od_worker_t *pool; atomic_uint_fast64_t round_robin; uint32_t count; }; static inline void od_worker_pool_init(od_worker_pool_t *pool) { pool->count = 0; pool->round_robin = 0; pool->pool = NULL; } static inline od_retcode_t od_worker_pool_start(od_worker_pool_t *pool, od_global_t *global, uint32_t count) { pool->pool = od_malloc(sizeof(od_worker_t) * count); if (pool->pool == NULL) { return -1; } pool->count = count; uint32_t i; for (i = 0; i < count; i++) { od_worker_t *worker = &pool->pool[i]; od_worker_init(worker, global, i); int rc; rc = od_worker_start(worker); if (rc == -1) { return NOT_OK_RESPONSE; } } return 0; } static inline void od_worker_pool_shutdown(od_worker_pool_t *pool) { for (uint32_t i = 0; i < pool->count; ++i) { od_worker_t *worker = &pool->pool[i]; od_worker_shutdown(worker); } } static inline void od_worker_pool_wait_gracefully_shutdown(od_worker_pool_t *pool) { for (uint32_t i = 0; i < pool->count; i++) { od_worker_t *worker = &pool->pool[i]; /* return code is not checked - the worker thread might already finished */ machine_wait_nb(worker->machine); machine_channel_free(worker->task_channel); } od_free(pool->pool); } static inline void od_worker_pool_feed(od_worker_pool_t *pool, machine_msg_t *msg) { uint64_t next; uint64_t oldValue; for (;;) { oldValue = atomic_load(&pool->round_robin); next = oldValue + 1 == pool->count ? 0 : oldValue + 1; if (atomic_compare_exchange_strong(&pool->round_robin, &oldValue, next)) { break; } } od_worker_t *worker; worker = &pool->pool[next]; machine_channel_write(worker->task_channel, msg); } odyssey-1.6.0-rc1/sources/include/xplan.h000066400000000000000000000106131526064202200203010ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include typedef enum { /* * nothing changed on entry execution */ OD_XPLAN_DELTA_NONE, /* * add pstmt to both client and server hashmaps * in case Parse for stmt which is not exists on server */ OD_XPLAN_DELTA_ADD_BOTH, /* * add pstmt only to client's hashmap * in case pstmt already existed on server */ OD_XPLAN_DELTA_ADD_CLIENT_ONLY, /* * add pstmt only to server's hashmap * for cases of shadow parsing of stmt, that is not * exists on server */ OD_XPLAN_DELTA_ADD_SERVER_ONLY, /* * remove pstmt from client map, on Close */ OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY, /* * remove all pstmts from both client and server hashmaps * in case of Execute(DISCARD ALL) */ OD_XPLAN_DELTA_REMOVE_ALL, /* * remove all pstmts from client hashmap only * in case of Execute(DEALLOCATE ALL) - virtualized, not sent to server */ OD_XPLAN_DELTA_REMOVE_CLIENT_ALL, /* * add portal -> pstmt mapping on client * on Bind (destination portal name) */ OD_XPLAN_DELTA_ADD_PORTAL, /* * remove portal -> pstmt mapping on client * on Close Portal or after Execute of DISCARD/DEALLOCATE */ OD_XPLAN_DELTA_REMOVE_PORTAL, /* * remove a single pstmt by name from client map * on Execute(DEALLOCATE name) */ OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME } od_xplan_delta_type_t; typedef struct { od_xplan_delta_type_t type; /* * client_pstmt name. normally points into the original client msg * (owned by xbuf), EXCEPT for OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME * where it is an owned NUL-terminated copy (freed in plan_entry_destroy). */ const char *client_pstmt; od_pstmt_t *pstmt; /* * portal name for ADD_PORTAL / REMOVE_PORTAL deltas * points to the original client msg (owned by xbuf) */ const char *portal_name; } od_xplan_delta_t; typedef enum { /* * send to backend and proxy the answer to client * this is: * - simple proxy without rewriting the msg: Execute, Flush, Sync, Describe(P) * - rewritten: Describe(S), Bind */ OD_XPLAN_FORWARD, /* * Parse with rewritten stmt name, * which must be added to client and server hashmaps * on success PC */ OD_XPLAN_PARSE, /* * inserted Parse, which PC must not be sent to client */ OD_XPLAN_PARSE_SHADOW, /* * CloseComplete for stmt on client */ OD_XPLAN_VIRTUAL_CLOSE_COMPLETE, /* * ParseComplete for stmt, that already exists on backend */ OD_XPLAN_VIRTUAL_PARSE_COMPLETE, /* * virtual ErrorResponse for unknown or already exists statements */ OD_XPLAN_VIRTUAL_ERROR_RESPONSE, /* * virtual CommandComplete for Execute of DEALLOCATE * that is virtualized (not sent to server) */ OD_XPLAN_VIRTUAL_COMMAND_COMPLETE, /* * deferred simple query begin */ OD_XPLAN_DEFERRED_BEGIN, } od_xplan_entry_type_t; typedef struct { od_xplan_entry_type_t type; /* * applied after receiving corresponding success response * from backend * * ex: applied with adding new pstmts after receiving * corresponding ParseComplete */ od_xplan_delta_t delta; /* * original msg from client * can be sent to server, if it wan't rewritten to server_msg * * cannot be NULL, but is owned by xbuf */ machine_msg_t *clmsg; /* * rewritten client msg to send to server * * can be NULL, owned by xplan */ machine_msg_t *srvmsg; /* * virtual response to client * note: * no ParseComplete msg here - it is always const seq of bytes * no CloseComplete msg here - it is always const seq of bytes * no CommandComplete msg here - it is always const seq of bytes * (handled by vcc_data, vcc_len) * * can be NULL, owned by xplan */ machine_msg_t *vresp; /* * pre-built CommandComplete wire bytes for * OD_XPLAN_VIRTUAL_COMMAND_COMPLETE. points to a static array, * not owned. NULL for all other entry types. */ const uint8_t *vcc_data; size_t vcc_len; } od_xplan_entry_t; struct od_xplan { /* od_xplan_entry_t[] */ mm_vector_t entries; }; void od_xplan_init(od_xplan_t *xp); void od_xplan_destroy(od_xplan_t *xp); void od_xplan_clear(od_xplan_t *xp); od_frontend_status_t od_xplan_make_from_xbuf(od_xplan_t *xp, od_relay_t *relay); od_frontend_status_t od_xplan_run(od_xplan_t *xp, od_relay_t *relay, uint32_t timeout_ms); odyssey-1.6.0-rc1/sources/instance.c000066400000000000000000000362421526064202200173410ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int od_cfg_import(od_logger_t *logger, od_config_t *config, od_rules_t *rules, od_global_t *global, od_hba_rules_t *hba_rules, const char *config_file) { od_cfg_model_t model; od_cfg_diag_list_t diags; od_cfg_model_init(&model); od_cfg_diag_list_init(&diags); int rc = od_cfg_parse_file(config_file, &model, &diags); for (size_t i = 0; i < diags.count; i++) { od_cfg_diag_t *d = &diags.items[i]; const char *fname = d->location.filename ? d->location.filename : config_file; if (d->level == OD_CFG_DIAG_WARNING) { od_log(logger, "config", NULL, NULL, "%s:%d:%d: warning: %s", fname, d->location.first_line, d->location.first_column, d->message); } else { od_error(logger, "config", NULL, NULL, "%s:%d:%d: %s", fname, d->location.first_line, d->location.first_column, d->message); } if (d->hint != NULL) { od_error(logger, "config", NULL, NULL, " hint: %s", d->hint); } } if (rc != 0 || od_cfg_diag_has_errors(&diags)) { od_cfg_diag_list_free(&diags); od_cfg_model_free(&model); return -1; } od_cfg_diag_list_free(&diags); od_cfg_diag_list_init(&diags); rc = od_cfg_convert_model(&model, config, rules, global, hba_rules, &diags); for (size_t i = 0; i < diags.count; i++) { od_cfg_diag_t *d = &diags.items[i]; const char *fname = d->location.filename ? d->location.filename : config_file; if (d->level == OD_CFG_DIAG_WARNING) { od_log(logger, "config", NULL, NULL, "%s:%d:%d: warning: %s", fname, d->location.first_line, d->location.first_column, d->message); } else { od_error(logger, "config", NULL, NULL, "%s:%d:%d: %s", fname, d->location.first_line, d->location.first_column, d->message); } if (d->hint != NULL) { od_error(logger, "config", NULL, NULL, " hint: %s", d->hint); } } int has_errors = od_cfg_diag_has_errors(&diags); od_cfg_diag_list_free(&diags); od_cfg_model_free(&model); return (rc != 0 || has_errors) ? -1 : 0; } void fill_supported_features_string(char *out, size_t max) { char *end = out + max; memset(out, 0, max); #ifdef PAM_FOUND out += od_snprintf(out, end - out, " +pam"); #else out += od_snprintf(out, end - out, " -pam"); #endif #ifdef LDAP_FOUND out += od_snprintf(out, end - out, " +ldap"); #else out += od_snprintf(out, end - out, " -ldap"); #endif #ifdef HAVE_SYSTEMD out += od_snprintf(out, end - out, " +systemd"); #else out += od_snprintf(out, end - out, " -systemd"); #endif #if defined(PROM_FOUND) && defined(PROM_HTTP_FOUND) out += od_snprintf(out, end - out, " +prom"); #else out += od_snprintf(out, end - out, " -prom"); #endif } static inline void free_cmdline(od_instance_t *instance) { if (instance->cmdline.envp != NULL) { int i = 0; while (instance->cmdline.envp[i] != NULL) { od_free(instance->cmdline.envp[i]); ++i; } od_free(instance->cmdline.envp); instance->cmdline.envp = NULL; } for (int i = 0; i < instance->cmdline.argc; ++i) { od_free(instance->cmdline.argv[i]); } od_free(instance->cmdline.argv); instance->cmdline.argv = NULL; instance->cmdline.argc = 0; } od_instance_t *od_instance_create(void) { od_instance_t *instance = od_malloc(sizeof(od_instance_t)); if (instance == NULL) { return NULL; } od_pid_init(&instance->pid); od_logger_init(&instance->logger, &instance->pid); od_config_init(&instance->config); instance->config_file = NULL; instance->pstmts = NULL; atomic_store(&instance->shutdown_worker_id, INVALID_COROUTINE_ID); atomic_store(&instance->config_load_failed, 0); instance->cmdline.argc = 0; instance->cmdline.argv = NULL; instance->cmdline.envp = NULL; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, OD_SIG_LOG_ROTATE); sigaddset(&mask, OD_SIG_ONLINE_RESTART); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGPIPE); sigprocmask(SIG_BLOCK, &mask, NULL); return instance; } void od_instance_free(od_instance_t *instance) { free_cmdline(instance); if (instance->config.pid_file && od_pid_restart_new_get(&instance->pid) == -1) { od_pid_unlink(&instance->pid, instance->config.pid_file); } od_config_free(&instance->config); if (instance->pstmts != NULL) { od_global_pstmts_map_free(instance->pstmts); } /* as mallocd on start */ od_free(instance->config_file); od_free(instance->exec_path); od_logger_close(&instance->logger); machinarium_free(); od_free(instance); } void od_usage(od_instance_t *instance, char *path) { od_log(&instance->logger, "init", NULL, NULL, "%s %s", ODYSSEY_NAME, ODYSSEY_VERSION_FULL); od_log(&instance->logger, "init", NULL, NULL, "usage: %s ", path); } int od_config_testing(od_instance_t *instance) { od_error_t error; od_router_t router; od_hba_t hba; od_global_t global; od_extension_t extensions; od_error_init(&error); od_router_init(&router, &global); od_hba_init(&hba); if (od_extensions_init(&extensions) != 0) { od_error(&instance->logger, "config", NULL, NULL, "failed to init extensions"); goto error; }; int rc; rc = od_cfg_import(&instance->logger, &instance->config, &router.rules, &global, &hba.rules, instance->config_file); if (rc == -1) { goto error; } /* validate configuration */ rc = od_config_validate(&instance->config, &instance->logger); if (rc == -1) { goto error; } /* validate rules */ rc = od_rules_validate(&router.rules, &instance->config, &instance->logger); if (rc == -1) { goto error; } od_log(&instance->logger, "config", NULL, NULL, "config is valid"); return 0; error: od_router_free(&router); return 1; } static inline od_retcode_t od_args_init(od_arguments_t *args, od_instance_t *instance) { args->silent = 0; args->verbose = 0; args->console = 0; args->test = 0; args->instance = instance; return OK_RESPONSE; } static inline int fill_cmdline(od_instance_t *instance, int argc, char **argv, char **envp) { instance->cmdline.argv = od_malloc(sizeof(char *) * (argc + 1 /* for NULL */)); if (instance->cmdline.argv == NULL) { return -1; } memset(instance->cmdline.argv, 0, sizeof(char *) * (argc + 1 /* for NULL */)); for (int i = 0; i < argc; ++i) { instance->cmdline.argv[i] = od_strdup(argv[i]); if (instance->cmdline.argv[i] == NULL) { instance->cmdline.argc = i; goto error; } } instance->cmdline.argv[argc] = NULL; instance->cmdline.argc = argc; int count = 0; while (envp[count] != NULL) { ++count; } instance->cmdline.envp = od_malloc(sizeof(char *) * (count + 1 /* for NULL */)); if (instance->cmdline.envp == NULL) { goto error; } memset(instance->cmdline.envp, 0, sizeof(char *) * (count + 1 /* for NULL */)); for (int i = 0; i < count; ++i) { instance->cmdline.envp[i] = od_strdup(envp[i]); if (instance->cmdline.envp[i] == NULL) { goto error; } } instance->cmdline.envp[count] = NULL; return 0; error: free_cmdline(instance); return -1; } char *od_instance_getenv(od_instance_t *instance, const char *name) { int len = strlen(name); for (int i = 0; instance->cmdline.envp[i] != NULL; ++i) { if (strncmp(instance->cmdline.envp[i], name, len) == 0 && instance->cmdline.envp[i][len] == '=') { return &(instance->cmdline.envp[i][len + 1]); } } return NULL; } int od_instance_main(od_instance_t *instance, int argc, char **argv, char **envp) { od_arguments_t args; memset(&args, 0, sizeof(args)); /* odyssey accept only ONE positional arg - to path config */ if (od_args_init(&args, instance) != OK_RESPONSE) { return NOT_OK_RESPONSE; } instance->exec_path = od_strdup(argv[0]); if (instance->exec_path == NULL) { return NOT_OK_RESPONSE; } /* validate command line options */ od_parse_args(argc, argv, &args); od_log(&instance->logger, "startup", NULL, NULL, "Starting Odyssey"); if (fill_cmdline(instance, argc, argv, envp) != 0) { od_error(&instance->logger, "startup", NULL, NULL, "can't preserve main arguments"); return NOT_OK_RESPONSE; } /* prepare system services */ od_router_t router; od_cron_t cron; od_worker_pool_t worker_pool; od_hba_t hba; od_extension_t *extensions = NULL; od_system_t *system = NULL; od_global_t *global = NULL; extensions = od_extensions_create(); if (extensions == NULL) { od_error(&instance->logger, "config", NULL, NULL, "failed to extensions init"); goto error; } system = od_system_create(); if (system == NULL) { goto error; } od_router_init(&router, NULL /* will set global later */); if (od_cron_init(&cron) != 0) { od_error(&instance->logger, "config", NULL, NULL, "failed to init cron"); goto error; } od_worker_pool_init(&worker_pool); od_hba_init(&hba); global = od_global_create(instance, system, &router, &cron, &worker_pool, extensions, &hba); if (global == NULL) { goto error; } router.global = global; /* read config file */ int rc; rc = od_cfg_import(&instance->logger, &instance->config, &router.rules, global, &hba.rules, instance->config_file); if (rc == -1) { goto error; } rc = od_apply_validate_cli_args(&instance->logger, &instance->config, &args, &router.rules); if (rc != OK_RESPONSE) { goto error; } /* validate configuration */ rc = od_config_validate(&instance->config, &instance->logger); if (rc == -1) { goto error; } /* validate rules */ rc = od_rules_validate(&router.rules, &instance->config, &instance->logger); if (rc == -1) { goto error; } /* auto-generate default rule for auth_query if none specified */ rc = od_rules_autogenerate_defaults(&router.rules, &instance->logger); od_rules_sort_for_matching(&router.rules); if (rc == -1) { goto error; } /* * run as daemon * should not daemonize when process was born by online restart */ if (instance->pid.restart_ppid == -1 && instance->config.daemonize) { rc = od_daemonize(); if (rc == -1) { goto error; } /* update pid */ od_pid_init(&instance->pid); } #ifdef PROM_FOUND rc = od_prom_metrics_init(cron.metrics); if (rc != OK_RESPONSE) { od_error(&instance->logger, "metrics", NULL, NULL, "failed to initialize metrics"); goto error; } #ifdef PROMHTTP_FOUND if (instance->config.log_route_stats_prom) { rc = od_prom_activate_route_metrics(cron.metrics); if (rc != OK_RESPONSE) { od_error(&instance->logger, "promhttp", NULL, NULL, "%s", "could not activate prom_http server"); goto error; } } else if (instance->config.log_general_stats_prom) { rc = od_prom_activate_general_metrics(cron.metrics); if (rc != OK_RESPONSE) { od_error(&instance->logger, "promhttp", NULL, NULL, "%s", "could not activate prom_http server"); goto error; } } #endif #endif /* reopen log file after config parsing */ if (instance->config.log_file) { rc = od_logger_open(&instance->logger, instance->config.log_file); if (rc == -1) { od_error(&instance->logger, "init", NULL, NULL, "failed to open log file '%s'", instance->config.log_file); goto error; } } /* configure logger */ od_logger_set_format(&instance->logger, instance->config.log_format); od_logger_set_debug(&instance->logger, instance->config.log_debug); od_logger_set_stdout(&instance->logger, instance->config.log_to_stdout); od_logger_set_async(&instance->logger, instance->config.log_async); od_logger_set_queue_depth(&instance->logger, instance->config.log_queue_depth); /* syslog */ if (instance->config.log_syslog) { od_logger_open_syslog(&instance->logger, instance->config.log_syslog_ident, instance->config.log_syslog_facility); } char features[128]; fill_supported_features_string(features, sizeof(features)); #ifdef ODYSSEY_VERSION_GIT od_log(&instance->logger, "init", NULL, NULL, "%s %s (git: %s) %s%s", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT, ODYSSEY_BUILD_TYPE, features); #else od_log(&instance->logger, "init", NULL, NULL, "%s %s %s%s", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_BUILD_TYPE, features); #endif /* print configuration */ od_log(&instance->logger, "init", NULL, NULL, "using configuration file '%s'", instance->config_file); if (instance->config.log_config) { od_config_print(&instance->config, &instance->logger); od_rules_print(&router.rules, &instance->logger); if (instance->config.hba_file) { od_hba_rules_print(&hba, &instance->logger); } } /* set process priority */ if (instance->config.priority != 0) { int rc; rc = setpriority(PRIO_PROCESS, 0, instance->config.priority); if (rc == -1) { od_error(&instance->logger, "init", NULL, NULL, "failed to set process priority: %s", strerror(errno)); goto error; } } /* initialize machinarium */ machinarium_set_stack_size(instance->config.coroutine_stack_size); machinarium_set_system_stack_size( instance->config.system_coroutine_stack_size); machinarium_set_pool_size(instance->config.resolvers); machinarium_set_coroutine_cache_size(instance->config.cache_coroutine); machinarium_set_msg_cache_gc_size(instance->config.cache_msg_gc_size); machinarium_set_msg_cache_gc_count(instance->config.cache_msg_gc_count); machinarium_set_dns_ttl_ms(instance->config.dns_ttl_ms); rc = machinarium_init(); if (rc == -1) { od_error(&instance->logger, "init", NULL, NULL, "failed to init machinarium"); goto error; } /* create pid file */ if (instance->config.pid_file) { rc = od_pid_create(&instance->pid, instance->config.pid_file); if (rc == -1) { od_error(&instance->logger, "init", NULL, NULL, "failed to create pid file %s: %s", instance->config.pid_file, strerror(errno)); goto error; } } /* start aync logging thread if needed */ od_logger_load(&instance->logger); if (instance->config.soft_oom.enabled) { rc = od_soft_oom_start_checker(&instance->config.soft_oom, &global->soft_oom); if (rc != OK_RESPONSE) { goto error; } } if (instance->config.host_watcher_enabled) { rc = od_host_watcher_init(&global->host_watcher); if (rc != OK_RESPONSE) { goto error; } } /* start system machine thread */ rc = od_system_start(system, global); if (rc == -1) { goto error; } rc = machine_wait(system->machine); return rc; error: /* TODO: proper cleanup od_router_free(&router); od_extension_free(&instance->logger, extensions); od_system_free(system); */ return NOT_OK_RESPONSE; } void od_instance_set_shutdown_worker_id(od_instance_t *instance, int64_t id) { atomic_store(&instance->shutdown_worker_id, id); } int64_t od_instance_get_shutdown_worker_id(od_instance_t *instance) { return atomic_load(&instance->shutdown_worker_id); } od_global_pstmt_map_t *od_instance_get_pstmts_map(od_instance_t *instance) { return instance->pstmts; } odyssey-1.6.0-rc1/sources/io.c000066400000000000000000000116631526064202200161440ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include static int write_do_duplex_step(od_io_t *io, int no_buf_fail) { int rc = od_io_try_read_some(io); if (rc != 0) { int errno_ = mm_errno_get(); if (errno_ == EWOULDBLOCK) { /* no data available, do nothing */ return 0; } if (errno_ == ENOBUFS) { /* * data is available, but there is no place in readahead for it * this is possible full-duplex deadlock, but for now just ignore */ return no_buf_fail; } /* some error occurred - go out */ return -1; } /* some data was read into readahead, that's good */ return 0; } int od_io_write_raw(od_io_t *io, const void *buf, size_t size, size_t *processed, uint32_t timeout_ms, int flags) { *processed = 0; size_t total = 0; const char *pos = buf; int duplex = flags & OD_IO_WRITE_DUPLEX; mm_io_set_deadline(io->io, timeout_ms); while (total < size) { int rc = machine_write_raw(io->io, pos, size - total, NULL /* TODO: processed */); if (rc > 0) { total += (size_t)rc; *processed = total; pos += rc; if (duplex && mm_io_read_ready(io->io)) { rc = write_do_duplex_step(io, 0); if (rc != 0) { return -1; } } continue; } int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { if (duplex && !mm_io_want_write(io->io)) { /* * only after not WANT_WRITE because if TLS is enabled * we must retry SSL_write with exact same arguments * after SSL_WANT_WRITE */ rc = write_do_duplex_step(io, 1); if (rc != 0) { return -1; } } rc = mm_io_wait_deadline(io->io); if (rc == MM_COND_WAIT_FAIL) { /* io wait will set errno to ETIMEDOUT or ECANCELLED */ return -1; } if (rc == MM_COND_WAIT_OK_PROPAGATED) { mm_errno_set(EAGAIN); return -1; } continue; } /* error or unexpected eof */ return -1; } return 0; } /* breaks iovec arg */ static struct iovec iov_advance(struct iovec *iovec, int cnt, ssize_t size) { while (cnt > 0) { if (iovec->iov_len > (size_t)size) { iovec->iov_base = (char *)iovec->iov_base + size; iovec->iov_len -= size; break; } size -= iovec->iov_len; ++iovec; --cnt; } struct iovec ret; ret.iov_base = iovec; ret.iov_len = cnt; return ret; } int od_io_writev(od_io_t *io, struct iovec *iov, int iovcnt, uint32_t timeout_ms, int flags) { int duplex = flags & OD_IO_WRITE_DUPLEX; mm_io_set_deadline(io->io, timeout_ms); while (iovcnt > 0) { ssize_t rc = machine_writev_raw(io->io, iov, iovcnt); if (rc > 0) { struct iovec a = iov_advance(iov, iovcnt, rc); iov = a.iov_base; iovcnt = a.iov_len; if (duplex && mm_io_read_ready(io->io)) { rc = write_do_duplex_step(io, 0); if (rc != 0) { return -1; } } continue; } int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { if (duplex && !mm_io_want_write(io->io)) { rc = write_do_duplex_step(io, 1); if (rc != 0) { return -1; } } rc = mm_io_wait_deadline(io->io); if (rc == MM_COND_WAIT_FAIL) { /* io wait will set errno to ETIMEDOUT or ECANCELLED */ return -1; } if (rc == MM_COND_WAIT_OK_PROPAGATED) { mm_errno_set(EAGAIN); return -1; } continue; } /* error or unexpected eof */ return -1; } return 0; } int od_io_read_some(od_io_t *io, uint32_t timeout_ms) { mm_io_set_deadline(io->io, timeout_ms); while (1) { struct iovec vec = od_readahead_write_begin(&io->readahead); if (vec.iov_len == 0) { return -1; } int rc = machine_read_raw(io->io, vec.iov_base, vec.iov_len); if (rc > 0) { od_readahead_write_commit(&io->readahead, (size_t)rc); return 0; } int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { rc = mm_io_wait_deadline(io->io); if (rc == MM_COND_WAIT_FAIL) { /* io wait will set errno to ETIMEDOUT or ECANCELLED */ return -1; } if (rc == MM_COND_WAIT_OK_PROPAGATED) { mm_errno_set(EAGAIN); return -1; } continue; } /* error or unexpected eof */ return -1; } abort(); } int od_io_try_read_some(od_io_t *io) { struct iovec vec = od_readahead_write_begin(&io->readahead); if (vec.iov_len == 0) { if (mm_io_read_pending(io->io)) { mm_errno_set(ENOBUFS); } else { mm_errno_set(EWOULDBLOCK); } return -1; } int rc = machine_read_raw(io->io, vec.iov_base, vec.iov_len); if (rc > 0) { od_readahead_write_commit(&io->readahead, (size_t)rc); return 0; } int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { mm_errno_set(EWOULDBLOCK); return -1; } /* error or unexpected eof */ return -1; } int od_io_write_flush(od_io_t *io, uint32_t timeout_ms) { const uint8_t flush[] = { KIWI_FE_FLUSH, 0, 0, 0, sizeof(uint32_t) }; od_assert(sizeof(flush) == 5); size_t unused; return od_io_write_raw(io, flush, sizeof(flush), &unused, timeout_ms, 0); } odyssey-1.6.0-rc1/sources/kiwi/000077500000000000000000000000001526064202200163255ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/kiwi/README.md000066400000000000000000000052441526064202200176110ustar00rootroot00000000000000**Kiwi** PostgreSQL protocol-level C library. Library is designed to provide most of the functionality needed to write or read [PostgreSQL protocol messages](https://www.postgresql.org/docs/9.6/static/protocol.html). Both Frontend (client to server) and Backend (server to client) messages are supported, making it possible to write client or server simulation applications. No network part is supported. Only buffer management and packet validation. Library is intedend to work in pair with the machinarium framework. **PostgreSQL packet readers** ```C /* Read initial message (StartupMessage, CancelRequest, SSLRequest) */ kiwi_read_startup() /* Read any other PostgreSQL packet */ kiwi_read() ``` **FRONTEND** **Write messages to Backend** ```C /* StartupMessage */ kiwi_fe_write_startup_message() /* CancelRequest */ kiwi_fe_write_cancel() /* SSLRequest */ kiwi_fe_write_ssl_request() /* Terminate */ kiwi_fe_write_terminate() /* PasswordMessage */ kiwi_fe_write_password() /* Query */ kiwi_fe_write_query() /* Query for prep stmt */ kiwi_fe_write_prep_stmt() /* Parse */ kiwi_fe_write_parse() /* Bind */ kiwi_fe_write_bind() /* Describe */ kiwi_fe_write_describe(); /* Execute */ kiwi_fe_write_execute(); /* Sync */ kiwi_fe_write_sync(); ``` **Read messages from Backend** ```C /* ReadyForQuery */ kiwi_fe_read_ready(); /* BackendKeyData */ kiwi_fe_read_key(); /* Authentication messages */ kiwi_fe_read_auth(); /* ParameterStatus */ kiwi_fe_read_parameter(); /* ErrorResponse */ kiwi_fe_read_error(); ``` **BACKEND** **Write messages to Frontend** ```C /* ErrorResponse */ kiwi_be_write_error() kiwi_be_write_error_fatal() kiwi_be_write_error_panic() /* NoticeResponse */ kiwi_be_write_notice() /* AuthenticationOk */ kiwi_be_write_authentication_ok() /* AuthenticationCleartextPassword */ kiwi_be_write_authentication_clear_text() /* AuthenticationMD5Password */ kiwi_be_write_authentication_md5() /* BackendKeyData */ kiwi_be_write_backend_key_data() /* ParameterStatus */ kiwi_be_write_parameter_status() /* EmptyQueryResponse */ kiwi_be_write_empty_query() /* CommandComplete */ kiwi_be_write_complete() /* ReadyForQuery */ kiwi_be_write_ready() /* ParseComplete */ kiwi_be_write_parse_complete() /* BindComplete */ kiwi_be_write_bind_complete() /* PortalSuspended */ kiwi_be_write_portal_suspended() /* NoData */ kiwi_be_write_no_data() /* RowDescription */ kiwi_be_write_row_description() kiwi_be_write_row_description_add() /* DataRow */ kiwi_be_write_data_row() kiwi_be_write_data_row_add() ``` **Read messages from Frontend** ```C /* Read StartupMessage, CancelRequest or SSLRequest */ kiwi_be_read_startup(); /* PasswordMessage */ kiwi_be_read_password(); ``` odyssey-1.6.0-rc1/sources/kiwi/md5.c000066400000000000000000000160571526064202200171670ustar00rootroot00000000000000 /* * kiwi. * * postgreSQL protocol interaction library. */ /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ /* Based on OpenBSD sys/crypto/md5 */ #include #define MD5_BLOCK_LENGTH 64 #define MD5_DIGEST_LENGTH 16 #define PUT_64BIT_LE(cp, value) \ do { \ (cp)[7] = (value) >> 56; \ (cp)[6] = (value) >> 48; \ (cp)[5] = (value) >> 40; \ (cp)[4] = (value) >> 32; \ (cp)[3] = (value) >> 24; \ (cp)[2] = (value) >> 16; \ (cp)[1] = (value) >> 8; \ (cp)[0] = (value); \ } while (0) #define PUT_32BIT_LE(cp, value) \ do { \ (cp)[3] = (value) >> 24; \ (cp)[2] = (value) >> 16; \ (cp)[1] = (value) >> 8; \ (cp)[0] = (value); \ } while (0) static uint8_t MD5_PADDING[MD5_BLOCK_LENGTH] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) #define MD5STEP(f, w, x, y, z, data, s) \ (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) static void kiwi_md5_transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH]) { uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; #if BYTE_ORDER == LITTLE_ENDIAN memcpy(in, block, sizeof(in)); #else for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { in[a] = (uint32_t)((uint32_t)(block[a * 4 + 0]) | (uint32_t)(block[a * 4 + 1]) << 8 | (uint32_t)(block[a * 4 + 2]) << 16 | (uint32_t)(block[a * 4 + 3]) << 24); } #endif a = state[0]; b = state[1]; c = state[2]; d = state[3]; MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); state[0] += a; state[1] += b; state[2] += c; state[3] += d; } void kiwi_md5_init(kiwi_md5_t *ctx) { ctx->count = 0; ctx->state[0] = 0x67452301; ctx->state[1] = 0xefcdab89; ctx->state[2] = 0x98badcfe; ctx->state[3] = 0x10325476; } void kiwi_md5_update(kiwi_md5_t *ctx, void *input_ptr, size_t len) { const uint8_t *input = input_ptr; size_t have, need; have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); need = MD5_BLOCK_LENGTH - have; ctx->count += (uint64_t)len << 3; if (len >= need) { if (have != 0) { memcpy(ctx->buffer + have, input, need); kiwi_md5_transform(ctx->state, ctx->buffer); input += need; len -= need; have = 0; } while (len >= MD5_BLOCK_LENGTH) { kiwi_md5_transform(ctx->state, input); input += MD5_BLOCK_LENGTH; len -= MD5_BLOCK_LENGTH; } } if (len != 0) { memcpy(ctx->buffer + have, input, len); } } void kiwi_md5_final(kiwi_md5_t *ctx, uint8_t digest[16]) { uint8_t count[8]; size_t padlen; int i; PUT_64BIT_LE(count, ctx->count); padlen = MD5_BLOCK_LENGTH - ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); if (padlen < 1 + 8) { padlen += MD5_BLOCK_LENGTH; } kiwi_md5_update(ctx, MD5_PADDING, padlen - 8); kiwi_md5_update(ctx, count, 8); for (i = 0; i < 4; i++) { PUT_32BIT_LE(digest + i * 4, ctx->state[i]); } } void kiwi_md5_tostring(char *dest, uint8_t digest[16]) { static const char *hex = "0123456789abcdef"; int q, w; for (q = 0, w = 0; q < 16; q++) { dest[w++] = hex[(digest[q] >> 4) & 0x0F]; dest[w++] = hex[(digest[q]) & 0x0F]; } } odyssey-1.6.0-rc1/sources/kiwi/options.c000066400000000000000000000070711526064202200201710ustar00rootroot00000000000000 /* * kiwi. * * postgreSQL protocol interaction library. */ #include static inline void kiwi_long_option_rewrite(char *name, int name_len) { assert(name); for (int i = 0; i < name_len; ++i) { if (name[i] == '-') { name[i] = '_'; } } } static inline const char *pgopts_find_token_end(const char *s, const char *end) { /* iterate forward until non-escaped space or end reached */ int escape = 0; while (s < end && *s != '\0') { if (isspace(*s) && !escape) { break; } if (!escape && *s == '\\') { escape = 1; } else { escape = 0; } ++s; } return s; } typedef struct { const char *ptr; size_t len; } tok_info_t; static inline tok_info_t pgopts_strtok(const char *str, const char *end, const char **prev) { /* * keep pg_split_opts space escaping logic * can't use strtok here because of that escaping * * interface is similar to strtok */ tok_info_t r; memset(&r, 0, sizeof(r)); if (str == NULL) { str = *prev; } while (str < end && isspace(*str)) { ++str; } if (str >= end || *str == '\0') { return r; } const char *e = pgopts_find_token_end(str, end); r.ptr = str; r.len = e - str; *prev = e + 1; return r; } static inline size_t find_eq_pos(const char *str, size_t len) { for (size_t i = 0; i < len; ++i) { if (str[i] == '=') { return i; } } return len; } static inline int unescape(const char *str, size_t len, char *dst, size_t dst_size, size_t *out_len) { int escape = 0; size_t rlen = 0; for (size_t i = 0; i < len; ++i) { if (!escape && str[i] == '\\') { escape = 1; } else { /* reserve one byte for terminating zero */ if (rlen + 1 >= dst_size) { return -1; } dst[rlen++] = str[i]; escape = 0; } } dst[rlen] = 0; *out_len = rlen; return 0; } static inline int kiwi_parse_option_and_update_var(kiwi_vars_t *vars, const char *str, size_t len) { char name[KIWI_MAX_VAR_SIZE]; char val[KIWI_MAX_VAR_SIZE]; size_t equal_pos = find_eq_pos(str, len); if (equal_pos == len) { /* invalid token */ return -1; } size_t nlen = equal_pos; size_t vlen = len - (equal_pos + 1); if (unescape(str, nlen, name, sizeof(name), &nlen) != 0) { return -1; } if (unescape(str + equal_pos + 1, vlen, val, sizeof(val), &vlen) != 0) { return -1; } kiwi_long_option_rewrite(name, (int)nlen); /* * options may contain arbitrary GUCs unknown to kiwi, * silently skip them, only malformed tokens are fatal here */ kiwi_vars_update(vars, name, (int)nlen + 1, val, (int)vlen + 1); return 0; } int kiwi_parse_options_and_update_vars(kiwi_vars_t *vars, const char *str, int slen) { if (str == NULL) { return -1; } const char *end = str + slen; const char *prev = NULL; int next_read = 0; int rc = 0; tok_info_t tokk = pgopts_strtok(str, end, &prev); while (tokk.ptr != NULL) { const char *tok = tokk.ptr; size_t len = tokk.len; if (next_read) { /* must read var afrer -c now */ next_read = 0; rc = kiwi_parse_option_and_update_var(vars, tok, len); if (rc != 0) { break; } } else if (len == 2 && tok[0] == '-' && tok[1] == 'c') { /* * this is -c var=value * must read value from next token */ next_read = 1; } else if (len > 2 && tok[0] == '-' && tok[1] == '-') { /* --var=value */ tok += 2; len -= 2; rc = kiwi_parse_option_and_update_var(vars, tok, len); if (rc != 0) { break; } } else { /* unexpected token */ return -1; } tokk = pgopts_strtok(NULL, end, &prev); } if (next_read) { /* expected key=value */ return -1; } return rc; } odyssey-1.6.0-rc1/sources/kiwi/var.c000066400000000000000000000064701526064202200172700ustar00rootroot00000000000000/* * kiwi. * * postgreSQL protocol interaction library. */ #include static inline int find_id_end(const char *str, int len, int pos) { /* * return the end position of id * * https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING * https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS */ static const char *id_start_alphabet = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_"; static const char *identifier_alphabet = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_0123456789"; if (pos >= len) { return pos; } /* must start with letter or _ */ if (strchr(id_start_alphabet, str[pos]) == NULL) { return pos; } ++pos; while (pos < len && strchr(identifier_alphabet, str[pos]) != NULL) { ++pos; } return pos; } static inline int is_safe_param(const char *str, int len) { /* * checks that the string contains only: * - english letters * - digits * - _ and , * - double quotes * - $, but not tags from dollar-enquoted strings * - spaces */ static const char *allowed_alphabet = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_0123456789,\" "; for (int i = 0; i < len; ++i) { if (str[i] == '$') { /* * it might be a dollar tag - forbidden entity * need to skip id (possible empty) and check if it * ends with $ * * ex: * - $$ * - $a$ * - $_a123$ */ ++i; /* skip $ */ int id_end = find_id_end(str, len, i + 1); if (id_end < len && str[id_end] == '$') { /* it is dollar tag, forbid */ return 0; } if (id_end >= len) { /* $aaaa\0 situation - ok */ return 1; } /* non-id symbol, need recheck that in allowed alphabet below */ i = id_end; /* no continue */ } if (strchr(allowed_alphabet, str[i]) == NULL) { return 0; } } return 1; } int kiwi_vars_cas(kiwi_vars_t *client, kiwi_vars_t *server, char *query, int query_len, int smart_enquoting_search_path) { int pos = 0; kiwi_var_type_t type; type = KIWI_VAR_CLIENT_ENCODING; for (; type < KIWI_VAR_MAX; type++) { kiwi_var_t *var; var = kiwi_vars_of(client, type); /* we do not support odyssey-to-backend compression yet */ if (var->type == KIWI_VAR_UNDEF || var->type == KIWI_VAR_COMPRESSION || var->type == KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS /* never deploy this one */) { continue; } kiwi_var_t *server_var; server_var = kiwi_vars_of(server, type); if (kiwi_var_compare(var, server_var)) { continue; } /* SET key=quoted_value; */ int size = 4 + (var->name_len - 1) + 1 + 1; if (query_len < size) { return -1; } memcpy(query + pos, "SET ", 4); pos += 4; memcpy(query + pos, var->name, var->name_len - 1); pos += var->name_len - 1; memcpy(query + pos, "=", 1); pos += 1; int quote_len; if (var->type != KIWI_VAR_SEARCH_PATH || !smart_enquoting_search_path || !is_safe_param(var->value, var->value_len)) { quote_len = kiwi_enquote(var->value, query + pos, query_len - pos); } else { if (query_len - pos < var->value_len) { return -1; } quote_len = var->value_len - 1; memcpy(query + pos, var->value, var->value_len - 1); } if (quote_len == -1) { return -1; } pos += quote_len; memcpy(query + pos, ";", 1); pos += 1; } return pos; } odyssey-1.6.0-rc1/sources/ldap.c000066400000000000000000000412431526064202200164520ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include static od_retcode_t od_ldap_error_report_client(od_client_t *cl, int rc) { if (rc == LDAP_SUCCESS) { return OK_RESPONSE; } od_gerror("auth", cl, NULL, "ldap authentication failed for user \"%s\": %s (%d)", cl->startup.user.value, ldap_err2string(rc), rc); od_frontend_fatal(cl, KIWI_SYSTEM_ERROR, "ldap authentication failed for user \"%s\": %s (%d)", cl->startup.user.value, ldap_err2string(rc), rc); return NOT_OK_RESPONSE; } od_retcode_t od_ldap_endpoint_prepare(od_ldap_endpoint_t *le) { const char *scheme; scheme = le->ldapscheme; /* ldap or ldaps */ if (scheme == NULL) { scheme = "ldap"; } le->ldapurl = NULL; if (!le->ldapserver) { /* TODO: support multiple ldap servers */ return NOT_OK_RESPONSE; } if (od_asprintf(&le->ldapurl, "%s://%s:%" PRIu64, scheme, le->ldapserver, le->ldapport) != OK_RESPONSE) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } od_retcode_t od_ldap_change_storage_credentials(od_logger_t *logger, od_ldap_storage_credentials_t *lsc, od_client_t *client) { client->ldap_storage_username = lsc->lsc_username; client->ldap_storage_username_len = strlen(lsc->lsc_username); client->ldap_storage_password = lsc->lsc_password; client->ldap_storage_password_len = strlen(lsc->lsc_password); od_debug(logger, "auth_ldap", client, NULL, "storage_user changed to %s", lsc->lsc_username); return OK_RESPONSE; } int search_storage_credentials(od_logger_t *logger, struct berval **values, od_rule_t *rule, od_client_t *client) { char host_db[128]; od_snprintf(host_db, sizeof(host_db), "%s_%s", rule->storage->host, client->startup.database.value); int len = ldap_count_values_len(values); if (len == 0) { od_error(logger, "auth_ldap", client, NULL, "empty search storage credentials result"); return LDAP_INSUFFICIENT_ACCESS; } for (int i = 0; i < len; i++) { if (strstr((char *)values[i]->bv_val, host_db)) { od_list_t *j; od_list_foreach (&rule->ldap_storage_creds_list, j) { od_ldap_storage_credentials_t *lsc = NULL; lsc = od_container_of( j, od_ldap_storage_credentials_t, link); char host_db_user[256]; od_snprintf(host_db_user, sizeof(host_db_user), "%s_%s", host_db, lsc->name); if (strstr((char *)values[i]->bv_val, host_db_user)) { od_debug(logger, "auth_ldap", client, NULL, "matched group %s", (char *)values[i]->bv_val); od_ldap_change_storage_credentials( logger, lsc, client); return LDAP_SUCCESS; } } } } od_error(logger, "auth_ldap", client, NULL, "error: route does not match any user attribute"); return LDAP_INSUFFICIENT_ACCESS; } /*---------------------------------------------------------------------------------------- */ /* ldap endpoints ADD/REMOVE API */ od_ldap_endpoint_t *od_ldap_endpoint_alloc(void) { od_ldap_endpoint_t *le = od_malloc(sizeof(od_ldap_endpoint_t)); if (le == NULL) { return NULL; } od_list_init(&le->link); le->name = NULL; le->ldapserver = NULL; le->ldapport = 0; le->ldapscheme = NULL; le->ldapprefix = NULL; le->ldapsuffix = NULL; le->ldapbindpasswd = NULL; le->ldapsearchfilter = NULL; le->ldapsearchattribute = NULL; le->ldapscope = NULL; le->ldapbasedn = NULL; le->ldapbinddn = NULL; /* preparsed connect url */ le->ldapurl = NULL; atomic_store(&le->refs, 1); return le; } od_ldap_endpoint_t *od_ldap_endpoint_ref(od_ldap_endpoint_t *le) { atomic_fetch_add(&le->refs, 1); return le; } od_retcode_t od_ldap_endpoint_free(od_ldap_endpoint_t *le) { if (atomic_fetch_sub(&le->refs, 1) > 1) { return OK_RESPONSE; } if (le->name) { od_free(le->name); } if (le->ldapserver) { od_free(le->ldapserver); } if (le->ldapscheme) { od_free(le->ldapscheme); } if (le->ldapprefix) { od_free(le->ldapprefix); } if (le->ldapsuffix) { od_free(le->ldapsuffix); } if (le->ldapbindpasswd) { od_free(le->ldapbindpasswd); } if (le->ldapsearchfilter) { od_free(le->ldapsearchfilter); } if (le->ldapsearchattribute) { od_free(le->ldapsearchattribute); } if (le->ldapscope) { od_free(le->ldapscope); } if (le->ldapbasedn) { od_free(le->ldapbasedn); } if (le->ldapbinddn) { od_free(le->ldapbinddn); } /* preparsed connect url */ if (le->ldapurl) { od_free(le->ldapurl); } od_list_unlink(&le->link); od_free(le); return OK_RESPONSE; } od_ldap_storage_credentials_t *od_ldap_storage_credentials_alloc(void) { od_ldap_storage_credentials_t *lsc = od_malloc(sizeof(od_ldap_storage_credentials_t)); if (lsc == NULL) { return NULL; } od_list_init(&lsc->link); lsc->name = NULL; lsc->lsc_username = NULL; lsc->lsc_password = NULL; atomic_store(&lsc->refs, 1); return lsc; } od_ldap_storage_credentials_t * od_ldap_storage_credentials_ref(od_ldap_storage_credentials_t *lsc) { atomic_fetch_add(&lsc->refs, 1); return lsc; } od_retcode_t od_ldap_storage_credentials_free(od_ldap_storage_credentials_t *lsc) { if (atomic_fetch_sub(&lsc->refs, 1) > 1) { return OK_RESPONSE; } if (lsc->name) { od_free(lsc->name); } if (lsc->lsc_username) { od_free(lsc->lsc_username); } if (lsc->lsc_password) { od_free(lsc->lsc_password); } od_list_unlink(&lsc->link); od_free(lsc); return OK_RESPONSE; } od_retcode_t od_ldap_endpoint_add(od_ldap_endpoint_t *ldaps, od_ldap_endpoint_t *target) { od_list_t *i; od_list_foreach (&(ldaps->link), i) { od_ldap_endpoint_t *s = od_container_of(i, od_ldap_endpoint_t, link); if (strcmp(s->name, target->name) == 0) { /* already loaded */ return NOT_OK_RESPONSE; } } od_list_append(&ldaps->link, &target->link); return OK_RESPONSE; } od_ldap_endpoint_t *od_ldap_endpoint_find(od_list_t *ldaps, char *name) { od_list_t *i; od_list_foreach (ldaps, i) { od_ldap_endpoint_t *serv = od_container_of(i, od_ldap_endpoint_t, link); if (strcmp(serv->name, name) == 0) { return serv; } } /* target ldap server was not found */ return NULL; } od_retcode_t od_ldap_endpoint_remove(od_ldap_endpoint_t *ldaps, od_ldap_endpoint_t *target) { od_list_t *i; od_list_foreach (&ldaps->link, i) { od_ldap_endpoint_t *serv = od_container_of(i, od_ldap_endpoint_t, link); if (strcmp(serv->name, target->name) == 0) { od_list_unlink(&target->link); return OK_RESPONSE; } } /* target ldap server was not found */ return NOT_OK_RESPONSE; } od_ldap_storage_credentials_t * od_ldap_storage_credentials_find(od_list_t *ldap_storage_creds_list, char *name) { od_list_t *i; od_list_foreach (ldap_storage_creds_list, i) { od_ldap_storage_credentials_t *lsc = od_container_of(i, od_ldap_storage_credentials_t, link); if (strcmp(lsc->name, name) == 0) { return lsc; } } /* target storage user was not found */ return NULL; } static od_thread_pool_t workers; int od_ldap_workers_init(size_t count) { return od_thread_pool_init(&workers, "ldap", count, 100); } void od_ldap_workers_destroy(void) { od_thread_pool_destroy(&workers); } static int is_username_valid(od_client_t *client, const char *name) { /* be sure LDAP-injection is not possible */ for (size_t i = 0; name[i] != '\0'; ++i) { char c = name[i]; if (c == '*' || c == '(' || c == ')' || c == '\\' || c == '/') { od_gerror( "auth_ldap", client, NULL, "invalid character in user name for LDAP authentication"); return 0; } } return 1; } static int init_and_bind(od_instance_t *instance, od_client_t *client, od_ldap_endpoint_t *endpoint, LDAP **conn, int timeout_sec) { struct timeval timeout_tv; timeout_tv.tv_sec = timeout_sec; timeout_tv.tv_usec = 0; *conn = NULL; int rc = ldap_initialize(conn, endpoint->ldapurl); if (rc != LDAP_SUCCESS) { od_error(&instance->logger, "auth_ldap", client, NULL, "ldap init failed: %d (%s)", rc, ldap_err2string(rc)); goto error; } int ldapversion = LDAP_VERSION3; rc = ldap_set_option(*conn, LDAP_OPT_PROTOCOL_VERSION, &ldapversion); if (rc != LDAP_SUCCESS) { goto error; } rc = ldap_set_option(*conn, LDAP_OPT_NETWORK_TIMEOUT, &timeout_tv); if (rc != LDAP_SUCCESS) { goto error; } rc = ldap_set_option(*conn, LDAP_OPT_TIMELIMIT, &timeout_sec); if (rc != LDAP_SUCCESS) { goto error; } rc = ldap_set_option(*conn, LDAP_OPT_TIMEOUT, &timeout_tv); if (rc != LDAP_SUCCESS) { goto error; } #ifdef LDAP_OPT_TCP_USER_TIMEOUT unsigned int tcp_usr_timeout_ms = timeout_sec * 1000; rc = ldap_set_option(*conn, LDAP_OPT_TCP_USER_TIMEOUT, &tcp_usr_timeout_ms); if (rc != LDAP_SUCCESS) { goto error; } #endif const char *binddn = endpoint->ldapbinddn ? endpoint->ldapbinddn : ""; const char *passwd = endpoint->ldapbindpasswd ? endpoint->ldapbindpasswd : ""; rc = ldap_simple_bind_s(*conn, binddn, passwd); if (rc != LDAP_SUCCESS) { od_error( &instance->logger, "auth_ldap", client, NULL, "fail to do initial LDAP bind for ldapbinddn \"%s\" on \"%s\": %d (%s)", binddn, endpoint->name, rc, ldap_err2string(rc)); goto error; } return LDAP_SUCCESS; error: if (*conn != NULL) { ldap_unbind_ext_s(*conn, NULL, NULL); } *conn = NULL; return rc; } static int resolve_user(od_instance_t *instance, LDAP *ldap, od_rule_t *rule, od_client_t *client) { od_ldap_endpoint_t *endp = rule->ldap_endpoint; if (endp->ldapbasedn == NULL) { char *auth_user = NULL; int rc = od_asprintf(&auth_user, "%s%s%s", endp->ldapprefix ? endp->ldapprefix : "", client->startup.user.value, endp->ldapsuffix ? endp->ldapsuffix : ""); if (rc == -1) { return LDAP_NO_MEMORY; } if (client->ldap_auth_dn != NULL) { od_free(client->ldap_auth_dn); } client->ldap_auth_dn = auth_user; return LDAP_SUCCESS; } /* * inspired by src/backend/libpq/auth.c */ char *filter = NULL; LDAPMessage *search_message; LDAPMessage *entry; char *attributes[] = { LDAP_NO_ATTRS, NULL }; char *dn; int count; if (rule->ldap_storage_credentials_attr) { attributes[0] = rule->ldap_storage_credentials_attr; } if (endp->ldapsearchattribute) { od_asprintf(&filter, "(%s=%s)", endp->ldapsearchattribute, client->startup.user.value); } else { od_asprintf(&filter, "(uid=%s)", client->startup.user.value); } if (filter == NULL) { return LDAP_NO_MEMORY; } if (endp->ldapsearchfilter) { char *prev_filter = od_strdup(filter); od_free(filter); if (prev_filter == NULL) { return LDAP_NO_MEMORY; } od_asprintf(&filter, "(&%s%s)", prev_filter, endp->ldapsearchfilter); od_free(prev_filter); if (filter == NULL) { return LDAP_NO_MEMORY; } } int rc = ldap_search_s(ldap, endp->ldapbasedn, LDAP_SCOPE_SUBTREE, filter, attributes, 0, &search_message); od_debug(&instance->logger, "auth_ldap", client, NULL, "basedn search entries with filter: %s and attrib %s", filter, attributes[0]); od_free(filter); if (rc != LDAP_SUCCESS) { od_error(&instance->logger, "auth_ldap", client, NULL, "basedn search failed: %d (%s)", rc, ldap_err2string(rc)); return rc; } count = ldap_count_entries(ldap, search_message); if (count != 1) { od_error(&instance->logger, "auth_ldap", client, NULL, "invalid basedn search entries count: %d", count); ldap_msgfree(search_message); return LDAP_INSUFFICIENT_ACCESS; } entry = ldap_first_entry(ldap, search_message); dn = ldap_get_dn(ldap, entry); if (dn == NULL) { int err = LDAP_OTHER; ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &err); od_error(&instance->logger, "auth_ldap", client, NULL, "ldap_get_dn failed: %d (%s)", err, ldap_err2string(err)); ldap_msgfree(search_message); return err; } if (rule->ldap_storage_credentials_attr) { struct berval **values = NULL; values = ldap_get_values_len(ldap, entry, attributes[0]); rc = search_storage_credentials(&instance->logger, values, rule, client); if (rc != LDAP_SUCCESS) { ldap_memfree(dn); ldap_value_free_len(values); ldap_msgfree(search_message); return rc; } ldap_value_free_len(values); } char *auth_user = od_strdup(dn); ldap_memfree(dn); ldap_msgfree(search_message); if (auth_user == NULL) { return LDAP_NO_MEMORY; } if (client->ldap_auth_dn != NULL) { od_free(client->ldap_auth_dn); } client->ldap_auth_dn = auth_user; return LDAP_SUCCESS; } static int ldap_auth_impl(od_client_t *client, kiwi_password_t *password) { od_instance_t *instance = client->global->instance; od_rule_t *rule = client->rule; LDAP *ldap = NULL; int rc = init_and_bind(instance, client, rule->ldap_endpoint, &ldap, 5 /* seconds */); if (rc != LDAP_SUCCESS) { goto error; } /* can be resolved at routing time */ if (client->ldap_auth_dn == NULL) { rc = resolve_user(instance, ldap, rule, client); if (rc != LDAP_SUCCESS) { goto error; } } rc = ldap_simple_bind_s(ldap, client->ldap_auth_dn, password->password); if (rc != LDAP_SUCCESS) { goto error; } ldap_unbind_ext_s(ldap, NULL, NULL); return LDAP_SUCCESS; error: od_error(&instance->logger, "auth_ldap", client, NULL, "ldap auth failed with code %d (%s)", rc, ldap_err2string(rc)); if (ldap != NULL) { ldap_unbind_ext_s(ldap, NULL, NULL); } return rc; } static int resolve_storage_credentials_impl(od_client_t *client, od_rule_t *rule) { od_instance_t *instance = client->global->instance; LDAP *ldap = NULL; int rc = init_and_bind(instance, client, rule->ldap_endpoint, &ldap, 5 /* seconds */); if (rc != LDAP_SUCCESS) { goto error; } /* will set client->ldap_auth_dn */ rc = resolve_user(instance, ldap, rule, client); if (rc != LDAP_SUCCESS) { goto error; } ldap_unbind_ext_s(ldap, NULL, NULL); return LDAP_SUCCESS; error: od_error(&instance->logger, "auth_ldap", client, NULL, "ldap storage crenedtials resolving failed with code %d (%s)", rc, ldap_err2string(rc)); if (ldap != NULL) { ldap_unbind_ext_s(ldap, NULL, NULL); } return rc; } typedef struct { od_client_t *client; kiwi_password_t *password; int rc; } auth_arg_t; static void *do_ldap_auth(void *a) { auth_arg_t *arg = a; arg->rc = ldap_auth_impl(arg->client, arg->password); return NULL; } int od_auth_ldap(od_client_t *client, kiwi_password_t *cl_password) { od_instance_t *instance = client->global->instance; od_rule_t *rule = client->rule; if (!is_username_valid(client, client->startup.user.value)) { return NOT_OK_RESPONSE; } auth_arg_t arg; memset(&arg, 0, sizeof(auth_arg_t)); arg.client = client; arg.password = cl_password; od_future_t *future = od_thread_pool_submit(&workers, do_ldap_auth, &arg, NULL, NULL, 0); if (future == NULL) { int err = mm_errno_get(); od_error(&instance->logger, "auth_ldap", client, NULL, "can't submit ldap auth task, errno=%d (%s)", err, strerror(err)); return NOT_OK_RESPONSE; } if (od_thread_pool_wait(future, UINT32_MAX)) { int err = mm_errno_get(); od_error(&instance->logger, "auth_ldap", client, NULL, "ldap auth wait error, errno=%d (%s)", err, strerror(err)); od_future_unref(future); return NOT_OK_RESPONSE; } od_future_unref(future); if (arg.rc != LDAP_SUCCESS && rule->client_fwd_error) { od_ldap_error_report_client(client, arg.rc); } return arg.rc == LDAP_SUCCESS ? OK_RESPONSE : NOT_OK_RESPONSE; } typedef struct { od_client_t *client; od_rule_t *rule; int rc; } resolve_storage_creds_arg_t; static void *do_resolve_storage_credentials(void *a) { resolve_storage_creds_arg_t *arg = a; arg->rc = resolve_storage_credentials_impl(arg->client, arg->rule); return NULL; } int od_auth_ldap_resolve_storage_credentials(od_client_t *client, od_rule_t *rule) { od_instance_t *instance = client->global->instance; if (!is_username_valid(client, client->startup.user.value)) { return NOT_OK_RESPONSE; } resolve_storage_creds_arg_t arg; memset(&arg, 0, sizeof(resolve_storage_creds_arg_t)); arg.client = client; arg.rule = rule; od_future_t *future = od_thread_pool_submit( &workers, do_resolve_storage_credentials, &arg, NULL, NULL, 0); if (future == NULL) { int err = mm_errno_get(); od_error(&instance->logger, "auth_ldap", client, NULL, "can't submit ldap auth task, errno=%d (%s)", err, strerror(err)); return NOT_OK_RESPONSE; } if (od_thread_pool_wait(future, UINT32_MAX)) { int err = mm_errno_get(); od_error(&instance->logger, "auth_ldap", client, NULL, "ldap auth wait error, errno=%d (%s)", err, strerror(err)); od_future_unref(future); return NOT_OK_RESPONSE; } od_future_unref(future); if (arg.rc != LDAP_SUCCESS && rule->client_fwd_error) { od_ldap_error_report_client(client, arg.rc); } return arg.rc == LDAP_SUCCESS ? OK_RESPONSE : NOT_OK_RESPONSE; } odyssey-1.6.0-rc1/sources/logger.c000066400000000000000000000732071526064202200170160ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #if defined(__linux__) #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct { char *name; int id; } od_log_syslog_facility_t; static od_log_syslog_facility_t od_log_syslog_facilities[] = { { "daemon", LOG_DAEMON }, { "user", LOG_USER }, { "local0", LOG_LOCAL0 }, { "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 }, { "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 }, { "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 }, { "local7", LOG_LOCAL7 }, { NULL, 0 } }; static int od_log_syslog_level[] = { LOG_INFO, LOG_ERR, LOG_DEBUG, LOG_CRIT }; static char *od_log_level[] = { "info", "error", "debug", "fatal" }; static int have_writev(int fd) { /* * not all fs have atomic writev impl * see `man 2 open`, the part about O_APPEND */ struct statfs sfs; if (fstatfs(fd, &sfs) == -1) { return 0; } #if defined(__linux__) /* man 2 fstatfs */ switch (sfs.f_type) { case 0xef53: /* EXT4_SUPER_MAGIC */ case 0x58465342: /* XFS_SUPER_MAGIC */ case 0x01021994: /* TMPFS_MAGIC */ return 1; default: return 0; } #else if (strcmp(sfs.f_fstypename, "apfs") == 0 || strcmp(sfs.f_fstypename, "hfs") == 0) { return 1; } return 0; #endif } od_retcode_t od_logger_init(od_logger_t *logger, od_pid_t *pid) { logger->pid = pid; logger->log_debug = 0; logger->log_stdout = 1; logger->log_syslog = 0; logger->format = NULL; logger->format_len = 0; logger->format_type = OD_LOGGER_FORMAT_TEXT; logger->tokens_count = 0; atomic_init(&logger->fd, -1); atomic_init(&logger->batching, 0); atomic_init(&logger->state, OD_LOGGER_OFFLINE); mm_mpsc_queue_init(&logger->tasks); logger->slots = NULL; logger->pending_slot = NULL; atomic_init(&logger->dropped_lines, 0); mm_lf_stack_init(&logger->free_slots); mm_wait_list_init(&logger->notifier, &logger->state); /* set temporary format */ od_logger_set_format(logger, "%p %t %l (%c) %h %m\n"); return OK_RESPONSE; } static inline void od_logger(void *arg); static inline od_fmt_token_type_t od_logger_format_parse_specifier(char c) { switch (c) { case 'p': return OD_FMT_PID; case 'T': return OD_FMT_TID; case 't': return OD_FMT_TIMESTAMP; case 'e': return OD_FMT_MILLIS; case 'n': return OD_FMT_UNIXTIME; case 'l': return OD_FMT_LEVEL; case 'c': return OD_FMT_CONTEXT; case 'm': return OD_FMT_MESSAGE; case 'M': return OD_FMT_MESSAGE_ESC; case 'i': return OD_FMT_CLIENT_ID; case 's': return OD_FMT_SERVER_ID; case 'u': return OD_FMT_USER; case 'd': return OD_FMT_DATABASE; case 'a': return OD_FMT_APPLICATION_NAME; case 'x': return OD_FMT_EXTERNAL_ID; case 'h': return OD_FMT_CLIENT_HOST; case 'r': return OD_FMT_CLIENT_PORT; case 'H': return OD_FMT_SERVER_HOST; default: return OD_FMT_LITERAL; } } void od_logger_set_format(od_logger_t *logger, char *format) { logger->tokens_count = 0; logger->format = format; logger->format_len = strlen(format); if (strcasestr(format, "json") != NULL) { logger->format_type = OD_LOGGER_FORMAT_JSON; return; } logger->format_type = OD_LOGGER_FORMAT_TEXT; int n = 0; const char *p = format; const char *end = format + logger->format_len; const char *lit_start = p; while (p < end) { if (*p == '\\') { if (p + 1 >= end) { p++; continue; } if (lit_start < p && n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = OD_FMT_LITERAL; logger->tokens[n].literal = lit_start; logger->tokens[n].literal_len = (int)(p - lit_start); n++; } lit_start = p; if (n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = OD_FMT_LITERAL; logger->tokens[n].literal = p; logger->tokens[n].literal_len = 2; n++; } p += 2; lit_start = p; continue; } if (*p == '%') { if (p + 1 >= end) { p++; continue; } char spec = p[1]; if (spec == '%') { if (lit_start < p && n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = OD_FMT_LITERAL; logger->tokens[n].literal = lit_start; logger->tokens[n].literal_len = (int)(p - lit_start); n++; } if (n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = OD_FMT_LITERAL; logger->tokens[n].literal = p; logger->tokens[n].literal_len = 1; n++; } p += 2; lit_start = p; continue; } if (lit_start < p && n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = OD_FMT_LITERAL; logger->tokens[n].literal = lit_start; logger->tokens[n].literal_len = (int)(p - lit_start); n++; } od_fmt_token_type_t type = od_logger_format_parse_specifier(spec); if (n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = type; logger->tokens[n].literal = NULL; logger->tokens[n].literal_len = 0; n++; } p += 2; lit_start = p; continue; } p++; } if (lit_start < p && n < OD_LOGGER_FORMAT_MAX_TOKENS) { logger->tokens[n].type = OD_FMT_LITERAL; logger->tokens[n].literal = lit_start; logger->tokens[n].literal_len = (int)(p - lit_start); n++; } logger->tokens_count = n; } od_retcode_t od_logger_load(od_logger_t *logger) { if (!logger->async) { return OK_RESPONSE; } if (atomic_load(&logger->state) == OD_LOGGER_ONLINE) { return NOT_OK_RESPONSE; } logger->slots = od_malloc(logger->queue_depth * sizeof(od_logger_slot_t)); if (logger->slots == NULL) { return NOT_OK_RESPONSE; } size_t n = (size_t)logger->queue_depth; for (size_t i = 0; i < n; ++i) { od_logger_slot_t *slot = &logger->slots[i]; memset(slot, 0, sizeof(od_logger_slot_t)); mm_lf_stack_push(&logger->free_slots, &slot->link); } char name[32]; od_snprintf(name, sizeof(name), "logger"); logger->machine = machine_create(name, od_logger, logger); if (logger->machine == -1) { od_free(logger->slots); return NOT_OK_RESPONSE; } return OK_RESPONSE; } int od_logger_open(od_logger_t *logger, const char *path) { int fd = open(path, O_RDWR | O_CREAT | O_APPEND | O_CLOEXEC, 0644); if (fd == -1) { return -1; } int batching = have_writev(fd); atomic_store(&logger->batching, batching); atomic_store(&logger->fd, fd); return 0; } int od_logger_reopen(od_logger_t *logger) { while (1) { uint64_t state = atomic_load(&logger->state); if (state != OD_LOGGER_ONLINE) { break; } if (atomic_compare_exchange_strong(&logger->state, &state, OD_LOGGER_REOPENING)) { mm_wait_list_notify(&logger->notifier); break; } } return 0; } int od_logger_open_syslog(od_logger_t *logger, char *ident, char *facility) { int facility_id = LOG_DAEMON; if (facility) { int i = 0; od_log_syslog_facility_t *facility_ptr; for (;;) { facility_ptr = &od_log_syslog_facilities[i]; if (facility_ptr->name == NULL) { break; } if (strcasecmp(facility_ptr->name, facility) == 0) { facility_id = facility_ptr->id; break; } i++; } } logger->log_syslog = 1; if (ident == NULL) { ident = "odyssey"; } openlog(ident, 0, facility_id); return 0; } void od_logger_close(od_logger_t *logger) { int fd = atomic_load(&logger->fd); atomic_store(&logger->fd, -1); atomic_store(&logger->batching, 0); if (fd != -1) { close(fd); } } static char od_logger_escape_tab[256] = { ['\0'] = '0', ['\t'] = 't', ['\n'] = 'n', ['\r'] = 'r', ['\\'] = '\\', ['='] = '=' }; __attribute__((hot)) static inline int od_logger_escape(char *dest, int size, char *fmt, va_list args) { char prefmt[512]; int prefmt_len; prefmt_len = od_vsnprintf(prefmt, sizeof(prefmt), fmt, args); char *dst_pos = dest; char *dst_end = dest + size; char *msg_pos = prefmt; char *msg_end = prefmt + prefmt_len; while (msg_pos < msg_end) { char escaped_char; escaped_char = od_logger_escape_tab[(unsigned char)*msg_pos]; if (od_unlikely(escaped_char)) { if (od_unlikely((dst_end - dst_pos) < 2)) { break; } dst_pos[0] = '\\'; dst_pos[1] = escaped_char; dst_pos += 2; } else { if (od_unlikely((dst_end - dst_pos) < 1)) { break; } dst_pos[0] = *msg_pos; dst_pos += 1; } msg_pos++; } return dst_pos - dest; } /* should be faster than od_snprintf("%s") */ static inline int od_logger_append_str(char *dst, char *dst_end, const char *s) { if (s == NULL) { s = "none"; } size_t len = strlen(s); size_t avail = (size_t)(dst_end - dst); if (len > avail) { len = avail; } memcpy(dst, s, len); return (int)len; } /* should be faster than od_snprintf("%s") */ static inline int od_logger_append_strn(char *dst, char *dst_end, const char *s, size_t len) { size_t avail = (size_t)(dst_end - dst); if (len > avail) { len = avail; } memcpy(dst, s, len); return (int)len; } /* should be faster than od_snprintf("%lu", val) */ static inline int od_logger_append_u64(char *dst, char *dst_end, uint64_t val) { char tmp[20]; int n = 0; do { tmp[n++] = (char)('0' + (val % 10)); val /= 10; } while (val); size_t avail = (size_t)(dst_end - dst); size_t w = 0; while (n > 0 && w < avail) { dst[w++] = tmp[--n]; } return (int)w; } /* should be faster than od_snprintf("%03d", val), width must be <= 10 */ static inline int od_logger_append_u32_padded(char *dst, char *dst_end, uint32_t val, int width) { char tmp[10]; int n = 0; do { tmp[n++] = (char)('0' + (val % 10)); val /= 10; } while (val); size_t avail = (size_t)(dst_end - dst); size_t w = 0; while (n < width && w < avail) { dst[w++] = '0'; width--; } while (n > 0 && w < avail) { dst[w++] = tmp[--n]; } return (int)w; } /* should be faster than od_snprintf("0x%" PRIx64, val) */ static inline int od_logger_append_hex64(char *dst, char *dst_end, uint64_t val) { static const char hex[] = "0123456789abcdef"; char tmp[16]; int n = 0; do { tmp[n++] = hex[val & 0xF]; val >>= 4; } while (val); size_t avail = (size_t)(dst_end - dst); size_t w = 0; if (w < avail) { dst[w++] = '0'; } if (w < avail) { dst[w++] = 'x'; } while (n > 0 && w < avail) { dst[w++] = tmp[--n]; } return (int)w; } /* should be faster than od_snprintf("%s%.*s", prefix, id_len, id) */ static inline int od_logger_append_id(char *dst, char *dst_end, const char *prefix, const char *id, int id_len) { int total = 0; if (prefix) { total += od_logger_append_str(dst, dst_end, prefix); dst += total; } total += od_logger_append_strn(dst, dst_end, id, id_len); return total; } __attribute__((hot)) static inline int od_logger_format(od_logger_t *logger, od_logger_level_t level, char *context, od_client_t *client, od_server_t *server, char *fmt, va_list args, char *output, int output_len) { char *dst_pos = output; char *dst_end = output + output_len; char peer[128]; /* Fast path: iterate over pre-compiled tokens. */ od_fmt_token_t *tokens = logger->tokens; int n = logger->tokens_count; /* Lazily fetch time info only if needed. */ struct timeval tv; int tv_fetched = 0; struct tm tm; int tm_parsed = 0; for (int i = 0; i < n; i++) { od_fmt_token_t *tok = &tokens[i]; switch (tok->type) { case OD_FMT_LITERAL: { int lit_len = tok->literal_len; const char *lit = tok->literal; int j = 0; while (j < lit_len) { if (lit[j] == '\\' && j + 1 < lit_len) { /* backslash escape: \n, \t, \r, \\ */ if (od_unlikely((dst_end - dst_pos) < 1)) { goto format_done; } switch (lit[j + 1]) { case '\\': *dst_pos++ = '\\'; break; case 'n': *dst_pos++ = '\n'; break; case 't': *dst_pos++ = '\t'; break; case 'r': *dst_pos++ = '\r'; break; default: if (od_unlikely((dst_end - dst_pos) < 2)) { goto format_done; } *dst_pos++ = '\\'; *dst_pos++ = lit[j + 1]; break; } j += 2; } else { if (od_unlikely((dst_end - dst_pos) < 1)) { goto format_done; } *dst_pos++ = lit[j]; j += 1; } } break; } case OD_FMT_PID: dst_pos += od_logger_append_strn(dst_pos, dst_end, logger->pid->pid_sz, logger->pid->pid_len); break; case OD_FMT_TID: dst_pos += od_logger_append_hex64( dst_pos, dst_end, (uint64_t)(uintptr_t)pthread_self()); break; case OD_FMT_TIMESTAMP: { if (!tv_fetched) { gettimeofday(&tv, NULL); tv_fetched = 1; } if (!tm_parsed) { gmtime_r(&tv.tv_sec, &tm); tm_parsed = 1; } int len = strftime(dst_pos, dst_end - dst_pos, "%FT%TZ", &tm); dst_pos += len; break; } case OD_FMT_MILLIS: { if (!tv_fetched) { gettimeofday(&tv, NULL); tv_fetched = 1; } dst_pos += od_logger_append_u32_padded( dst_pos, dst_end, (uint32_t)((signed)tv.tv_usec / 1000), 3); break; } case OD_FMT_UNIXTIME: { if (!tv_fetched) { gettimeofday(&tv, NULL); tv_fetched = 1; } dst_pos += od_logger_append_u64(dst_pos, dst_end, (uint64_t)tv.tv_sec); break; } case OD_FMT_LEVEL: dst_pos += od_logger_append_str(dst_pos, dst_end, od_log_level[level]); break; case OD_FMT_CONTEXT: dst_pos += od_logger_append_str(dst_pos, dst_end, context); break; case OD_FMT_MESSAGE: dst_pos += od_vsnprintf(dst_pos, dst_end - dst_pos, fmt, args); break; case OD_FMT_MESSAGE_ESC: dst_pos += od_logger_escape(dst_pos, dst_end - dst_pos, fmt, args); break; case OD_FMT_CLIENT_ID: if (client && client->id.id_prefix != NULL) { dst_pos += od_logger_append_id( dst_pos, dst_end, client->id.id_prefix, client->id.id, (int)sizeof(client->id.id)); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_SERVER_ID: if (server && server->id.id_prefix != NULL) { dst_pos += od_logger_append_id( dst_pos, dst_end, server->id.id_prefix, server->id.id, (int)sizeof(server->id.id)); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_USER: if (client && client->startup.user.value_len) { dst_pos += od_logger_append_str( dst_pos, dst_end, client->startup.user.value); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_DATABASE: if (client && client->startup.database.value_len) { dst_pos += od_logger_append_str( dst_pos, dst_end, client->startup.database.value); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_APPLICATION_NAME: { kiwi_var_t *var = NULL; if (client) { var = kiwi_vars_get(&client->vars, KIWI_VAR_APPLICATION_NAME); } dst_pos += od_logger_append_str( dst_pos, dst_end, var ? var->value : "none"); break; } case OD_FMT_EXTERNAL_ID: if (client && client->external_id != NULL) { dst_pos += od_logger_append_str( dst_pos, dst_end, client->external_id); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_SERVER_HOST: if (client && client->route) { od_rule_storage_t *storage = client->route->rule->storage; if (client->server != NULL && client->server->endpoint != NULL) { storage = client->server->endpoint ->storage; } dst_pos += od_logger_append_str( dst_pos, dst_end, storage ? storage->host : "none"); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_CLIENT_HOST: if (client && client->io.io) { od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); dst_pos += od_logger_append_str(dst_pos, dst_end, peer); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; case OD_FMT_CLIENT_PORT: if (client && client->io.io) { od_getpeername(client->io.io, peer, sizeof(peer), 0, 1); dst_pos += od_logger_append_str(dst_pos, dst_end, peer); } else { dst_pos += od_logger_append_str( dst_pos, dst_end, "none"); } break; } } format_done: /* append new line, if format string doesn't have it */ if (dst_pos < dst_end && dst_pos > output && *(dst_pos - 1) != '\n') { *dst_pos = '\n'; ++dst_pos; } return dst_pos - output; } static inline void _od_logger_write_batch(od_logger_t *l, od_logger_slot_t *slots[], struct iovec *iovecs, size_t n) { int fd = atomic_load(&l->fd); int batching = atomic_load(&l->batching); int rc; if (fd != -1) { if (batching) { rc = writev(fd, iovecs, n); } else { for (size_t i = 0; i < n; ++i) { struct iovec *vec = &iovecs[i]; rc = write(fd, vec->iov_base, vec->iov_len); if (rc <= 0) { break; } } } } if (l->log_stdout) { rc = writev(STDOUT_FILENO, iovecs, n); } if (l->log_syslog) { for (size_t i = 0; i < n; ++i) { syslog(od_log_syslog_level[slots[i]->level], "%.*s", (int)iovecs[i].iov_len, (char *)iovecs[i].iov_base); } } (void)rc; } static inline void _od_logger_write(od_logger_t *l, char *data, int len, od_logger_level_t lvl) { int fd = atomic_load(&l->fd); int rc; if (fd != -1) { rc = write(fd, data, len); } if (l->log_stdout) { rc = write(STDOUT_FILENO, data, len); } if (l->log_syslog) { syslog(od_log_syslog_level[lvl], "%.*s", len, data); } (void)rc; } static inline void log_machine_stats(od_logger_t *logger) { uint64_t count_coroutine = 0; uint64_t count_coroutine_cache = 0; uint64_t msg_allocated = 0; uint64_t msg_cache_count = 0; uint64_t msg_cache_gc_count = 0; uint64_t msg_cache_size = 0; machine_stat(&count_coroutine, &count_coroutine_cache, &msg_allocated, &msg_cache_count, &msg_cache_gc_count, &msg_cache_size); od_log(logger, "stats", NULL, NULL, "logger: msg (%" PRIu64 " allocated, %" PRIu64 " cached, %" PRIu64 " freed, %" PRIu64 " cache_size), " "coroutines (%" PRIu64 " active, %" PRIu64 " cached), " "dropped lines %" PRIu64 ", queue size %zu", msg_allocated, msg_cache_count, msg_cache_gc_count, msg_cache_size, count_coroutine, count_coroutine_cache, atomic_load(&logger->dropped_lines), mm_mpsc_queue_size(&logger->tasks)); } void od_logger_stat(od_logger_t *logger) { log_machine_stats(logger); } static void process_log_queue(od_logger_t *logger, od_logger_slot_t **slot_buf, struct iovec *iovecs, size_t max) { size_t nmsg = 0; while (nmsg < max) { mm_mpsc_node_t *n = mm_mpsc_queue_pop(&logger->tasks); if (n == NULL) { break; } slot_buf[nmsg] = od_container_of(n, od_logger_slot_t, node); nmsg++; } if (nmsg == 0) { return; } for (size_t i = 0; i < nmsg; ++i) { iovecs[i].iov_base = slot_buf[i]->text; iovecs[i].iov_len = slot_buf[i]->len; } _od_logger_write_batch(logger, slot_buf, iovecs, nmsg); /* * The last popped slot becomes the new pending_slot: it is now * the queue's head sentinel, and its `next` may still be written * by a producer. It will be recycled in the next call. */ for (size_t i = 0; i + 1 < nmsg; ++i) { mm_lf_stack_push(&logger->free_slots, &slot_buf[i]->link); } if (logger->pending_slot) { mm_lf_stack_push(&logger->free_slots, &logger->pending_slot->link); } logger->pending_slot = slot_buf[nmsg - 1]; } static void do_reopen_logfile(od_logger_t *logger) { const char *path = od_global_get_instance()->config.log_file; int old = atomic_load(&logger->fd); int rc = od_logger_open(logger, path); if (rc == 0) { close(old); od_log(logger, "logger", NULL, NULL, "log reopened"); } else { /* do nothing, keep use old file */ od_error(logger, "logger", NULL, NULL, "failed to reopen log file '%s'", path); } } static inline void od_logger(void *arg) { od_logger_t *logger = arg; static od_logger_slot_t *slot_buf[IOV_MAX]; static struct iovec iovecs[IOV_MAX]; atomic_store(&logger->state, OD_LOGGER_ONLINE); while (1) { uint64_t state = atomic_load(&logger->state); if (state == OD_LOGGER_OFFLINE) { break; } if (state == OD_LOGGER_REOPENING) { do_reopen_logfile(logger); /* do not check - the state might have been changed to CLOSED */ atomic_compare_exchange_strong(&logger->state, &state, OD_LOGGER_ONLINE); continue; } process_log_queue(logger, slot_buf, iovecs, IOV_MAX); if (mm_mpsc_queue_empty(&logger->tasks)) { mm_wait_list_compare_wait( &logger->notifier, NULL, OD_LOGGER_ONLINE /* still online? */, 500); } } /* * process messages that stays in queue after shutdown * did not writen smth like plain while (queue size > 0) * because i want to be sure that this function will return in case * some bug with state * * divide by IOV_MAX because this is max chunk size * of process_log_queue */ size_t tail = mm_mpsc_queue_size(&logger->tasks); tail = 2 * ((tail + IOV_MAX - 1) / IOV_MAX); for (size_t i = 0; i < tail && mm_mpsc_queue_size(&logger->tasks) > 0; ++i) { process_log_queue(logger, slot_buf, iovecs, IOV_MAX); } } void od_logger_shutdown(od_logger_t *logger) { atomic_store(&logger->state, OD_LOGGER_OFFLINE); mm_wait_list_notify(&logger->notifier); } void od_logger_flush(od_logger_t *logger) { if (!logger->async) { return; } static od_logger_slot_t *slot_buf[IOV_MAX]; static struct iovec iovecs[IOV_MAX]; process_log_queue(logger, slot_buf, iovecs, IOV_MAX); } void od_logger_wait_finish(od_logger_t *logger) { if (!logger->async) { return; } if (machine_wait(logger->machine)) { abort(); } mm_wait_list_destroy(&logger->notifier); logger->pending_slot = NULL; od_free(logger->slots); } static char od_logger_json_escape_tab[256] = { ['"'] = '"', ['\\'] = '\\', ['/'] = '/', ['\b'] = 'b', ['\f'] = 'f', ['\n'] = 'n', ['\r'] = 'r', ['\t'] = 't' }; __attribute__((hot)) static inline char * od_logger_json_append_escaped(char *dst, char *dst_end, const char *src) { if (!src) { return dst; } while (*src && dst < dst_end) { char escaped = od_logger_json_escape_tab[(unsigned char)*src]; if (escaped) { if (dst + 2 >= dst_end) { break; } *dst++ = '\\'; *dst++ = escaped; } else if ((unsigned char)*src < 0x20) { if (dst + 6 >= dst_end) { break; } dst += snprintf(dst, 7, "\\u%04x", (unsigned char)*src); } else { *dst++ = *src; } src++; } return dst; } __attribute__((hot)) static inline char * od_logger_json_add_string(char *dst, char *dst_end, const char *key, const char *value, int add_comma) { if (!value || dst >= dst_end) { return dst; } if (add_comma && dst < dst_end) { *dst++ = ','; } if (dst < dst_end) { *dst++ = '"'; } dst = od_logger_json_append_escaped(dst, dst_end, key); if (dst < dst_end) { *dst++ = '"'; } if (dst < dst_end) { *dst++ = ':'; } if (dst < dst_end) { *dst++ = '"'; } dst = od_logger_json_append_escaped(dst, dst_end, value); if (dst < dst_end) { *dst++ = '"'; } return dst; } __attribute__((hot)) static inline int od_logger_format_json(od_logger_t *logger, od_logger_level_t level, char *context, void *client_ptr, void *server_ptr, char *fmt, va_list args, char *output, int output_len) { od_client_t *client = client_ptr; od_server_t *server = server_ptr; char *dst = output; char *dst_end = output + output_len - 2; int add_comma = 0; if (dst < dst_end) { *dst++ = '{'; } /* timestamp */ struct timeval tv; gettimeofday(&tv, NULL); struct tm tm; char tmp_buf[64]; strftime(tmp_buf, sizeof(tmp_buf), "%FT%TZ", gmtime_r(&tv.tv_sec, &tm)); dst = od_logger_json_add_string(dst, dst_end, "timestamp", tmp_buf, add_comma); add_comma = 1; dst = od_logger_json_add_string(dst, dst_end, "pid", logger->pid->pid_sz, add_comma); memset(tmp_buf, 0, sizeof(tmp_buf)); od_snprintf(tmp_buf, sizeof(tmp_buf), "0x%" PRIx64, (uint64_t)(uintptr_t)pthread_self()); dst = od_logger_json_add_string(dst, dst_end, "tid", tmp_buf, add_comma); dst = od_logger_json_add_string(dst, dst_end, "level", od_log_level[level], add_comma); dst = od_logger_json_add_string(dst, dst_end, "context", context, add_comma); if (dst < dst_end) { *dst++ = ','; } if (dst < dst_end) { *dst++ = '"'; } dst = od_logger_json_append_escaped(dst, dst_end, "message"); if (dst < dst_end) { *dst++ = '"'; } if (dst < dst_end) { *dst++ = ':'; } if (dst < dst_end) { *dst++ = '"'; } char message[1024]; int msg_len = vsnprintf(message, sizeof(message), fmt, args); if (msg_len >= (int)sizeof(message)) { msg_len = sizeof(message) - 1; } dst = od_logger_json_append_escaped(dst, dst_end, message); if (dst < dst_end) { *dst++ = '"'; } /* client fields */ if (client) { if (dst < dst_end) { *dst++ = ','; } if (dst < dst_end) { *dst++ = '"'; } dst = od_logger_json_append_escaped(dst, dst_end, "client"); if (dst < dst_end) { *dst++ = '"'; } if (dst < dst_end) { *dst++ = ':'; } if (dst < dst_end) { *dst++ = '{'; } int client_comma = 0; if (client->id.id_prefix) { char client_id[64]; snprintf(client_id, sizeof(client_id), "%s%.*s", client->id.id_prefix, (int)sizeof(client->id.id), client->id.id); dst = od_logger_json_add_string( dst, dst_end, "id", client_id, client_comma); client_comma = 1; } if (client->io.io) { char peer[64]; od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); dst = od_logger_json_add_string(dst, dst_end, "ip", peer, client_comma); client_comma = 1; od_getpeername(client->io.io, peer, sizeof(peer), 0, 1); dst = od_logger_json_add_string(dst, dst_end, "port", peer, client_comma); } if (client->startup.user.value_len) { dst = od_logger_json_add_string( dst, dst_end, "user", client->startup.user.value, client_comma); client_comma = 1; } if (client->startup.database.value_len) { dst = od_logger_json_add_string( dst, dst_end, "database", client->startup.database.value, client_comma); client_comma = 1; } if (client->external_id) { dst = od_logger_json_add_string(dst, dst_end, "external_id", client->external_id, client_comma); client_comma = 1; } if (client->route && client->route->rule && client->route->rule->storage) { od_rule_storage_t *storage = client->route->rule->storage; if (client->server != NULL && client->server->endpoint != NULL) { storage = client->server->endpoint->storage; } dst = od_logger_json_add_string( dst, dst_end, "server_host", storage ? storage->host : "none", client_comma); } /* Close client object */ if (dst < dst_end) { *dst++ = '}'; } } /* server fields */ if (server) { if (dst < dst_end) { *dst++ = ','; } if (dst < dst_end) { *dst++ = '"'; } dst = od_logger_json_append_escaped(dst, dst_end, "server"); if (dst < dst_end) { *dst++ = '"'; } if (dst < dst_end) { *dst++ = ':'; } if (dst < dst_end) { *dst++ = '{'; } int server_comma = 0; if (server->id.id_prefix) { char server_id[64]; snprintf(server_id, sizeof(server_id), "%s%.*s", server->id.id_prefix, (int)sizeof(server->id.id), server->id.id); dst = od_logger_json_add_string( dst, dst_end, "id", server_id, server_comma); } if (dst < dst_end) { *dst++ = '}'; } } if (dst < dst_end) { *dst++ = '}'; } if (dst < dst_end) { *dst++ = '\n'; } return dst - output; } void od_logger_write(od_logger_t *logger, od_logger_level_t level, char *context, void *client, void *server, char *fmt, va_list args) { if (logger == OD_LOGGER_GLOBAL) { logger = od_global_get_logger(); } int fd = atomic_load(&logger->fd); if (fd == -1 && !logger->log_stdout && !logger->log_syslog) { return; } if (logger->format_type == OD_LOGGER_FORMAT_JSON && fmt && fmt[0] == '\0') { return; } if (level == OD_DEBUG) { int is_debug = logger->log_debug; if (!is_debug) { od_client_t *client_ref = client; od_server_t *server_ref = server; if (client_ref && client_ref->rule) { is_debug = client_ref->rule->log_debug; } else if (server_ref && server_ref->route) { od_route_t *route = server_ref->route; is_debug = route->rule->log_debug; } } if (!is_debug) { return; } } int len; char *output; size_t output_max; od_logger_slot_t *async_slot = NULL; uint64_t state = atomic_load(&logger->state); uint64_t async = (state == OD_LOGGER_ONLINE) || (state == OD_LOGGER_REOPENING); if (level == OD_FATAL) { async = 0; } if (async) { mm_lf_stack_entry_t *e = mm_lf_stack_pop(&logger->free_slots); if (e) { async_slot = od_container_of(e, od_logger_slot_t, link); } if (async_slot == NULL) { /* silently drop lines for overloaded logger */ atomic_fetch_add_explicit(&logger->dropped_lines, 1, memory_order_relaxed); return; } output = async_slot->text; output_max = sizeof(async_slot->text); } else { static OD_THREAD_LOCAL char localoutput[OD_LOGLINE_MAXLEN]; output = localoutput; output_max = sizeof(localoutput); } /* Choose formatter based on format type */ if (logger->format_type == OD_LOGGER_FORMAT_JSON) { len = od_logger_format_json(logger, level, context, client, server, fmt, args, output, output_max); } else { len = od_logger_format(logger, level, context, client, server, fmt, args, output, output_max); } if (async_slot) { async_slot->len = (size_t)len; async_slot->text[len] = '\0'; async_slot->level = level; size_t sz = mm_mpsc_queue_push(&logger->tasks, &async_slot->node); if (sz >= 30) { mm_wait_list_notify(&logger->notifier); } } else { _od_logger_write(logger, output, len, level); } } odyssey-1.6.0-rc1/sources/machinarium/000077500000000000000000000000001526064202200176575ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/machinarium/README.md000066400000000000000000000047341526064202200211460ustar00rootroot00000000000000## Machinarium Machinarium allows to create fast networked and event-driven asynchronous applications in synchronous/procedural manner instead of using traditional callback approach. *Public API ⇨* [sources/machinarium.h](sources/machinarium.h) #### Threads and coroutines Machinarium is based on combination of `pthreads(7)` and custom made implementation of efficient cooperative multi-tasking primitives (coroutines). Each coroutine executed using own stack context and transparently scheduled by `epoll(7)` event-loop logic. Each working Machinarium thread can handle thousands of executing coroutines. #### Messaging and Channels Machinarium messages and channels are used to provide IPC between threads and coroutines. Ideally, this approach should be sufficient to fulfill needs of most multi-threaded applications without the need of using additional access synchronization. #### Efficient TCP/IP networking Machinarium IO API primitives can be used to develop high-performance client and server applications. By doing blocking IO calls, currently executing coroutine suspends and switches to a next one ready to go. When the original request completed, timedout or cancel event happened, coroutine resumes. One of the main goals of networking API design is performance. To reduce number of system calls read operation implemented with readahead support. It is fully buffered and transparently continue to read socket data even when no active calls are in progress, to reduce `epoll(7)` subscribe overhead. Machinarium IO contexts can be transferred between threads, which allows to develop efficient producer-consumer network applications. #### Full-featured SSL/TLS support Machinarium has easy-to-use Transport Layer Security (TLS) API methods. Create Machinarium TLS object, associate it with any existing IO context and the connection will be automatically upgraded. #### DNS resolving Machinarium implements separate thread-pool to run network address and service translation functions such as `getaddrinfo()`, to avoid process blocking and to be consistent with coroutine design. #### Timeouts and Cancellation All blocking Machinarium API methods are designed with timeout flag. If operation does not complete during requested time interval, then method will return with appropriate `errno` status set. Additionally, a coroutine can `Cancel` any on-going blocking call of some other coroutine. And as in timeout handling, cancelled coroutine method will return with appropriate status. odyssey-1.6.0-rc1/sources/machinarium/accept.c000066400000000000000000000040471526064202200212670ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include MACHINE_API int mm_io_accept(mm_io_t *obj, mm_io_t **client, int backlog, int attach, uint32_t time_ms) { mm_io_t *io = mm_cast(mm_io_t *, obj); int rc, fd; mm_errno_set(0); if (mm_call_is_active(&io->call)) { mm_errno_set(EINPROGRESS); return -1; } if (io->connected) { mm_errno_set(EINPROGRESS); return -1; } if (io->fd == -1) { mm_errno_set(EBADF); return -1; } if (!io->accept_listen) { rc = mm_socket_listen(io->fd, backlog); if (rc == -1) { mm_errno_set(errno); return -1; } io->accept_listen = 1; } if (!io->attached) { rc = mm_io_attach(io); if (rc == -1) { mm_errno_set(errno); return -1; } } mm_io_set_deadline(io, time_ms); while (1) { fd = mm_socket_accept(io->fd, NULL, NULL); if (fd > 0) { break; } int err = errno; if (machine_accept_errno_retryable(err)) { /* wait for EPOLLIN event on socket */ rc = mm_io_wait_deadline(io); if (rc == MM_COND_WAIT_OK_PROPAGATED) { mm_errno_set(EAGAIN); return -1; } if (rc != 0) { return -1; } continue; } mm_errno_set(err); return -1; } /* setup client io */ *client = mm_io_create(); if (*client == NULL) { mm_errno_set(ENOMEM); return -1; } mm_io_t *client_io; client_io = (mm_io_t *)*client; client_io->is_unix_socket = io->is_unix_socket; client_io->opt_nodelay = io->opt_nodelay; client_io->opt_keepalive = io->opt_keepalive; client_io->opt_keepalive_delay = io->opt_keepalive_delay; client_io->accepted = 1; client_io->connected = 1; rc = mm_io_socket_set(client_io, fd); if (rc == -1) { mm_io_close(*client); mm_io_free(*client); *client = NULL; return -1; } if (attach) { rc = mm_io_attach((mm_io_t *)client_io); if (rc == -1) { mm_io_close(*client); mm_io_free(*client); *client = NULL; return -1; } } return 0; } odyssey-1.6.0-rc1/sources/machinarium/backtrace.c000066400000000000000000000024231526064202200217430ustar00rootroot00000000000000#include #include #include #if defined(__GLIBC__) #include #endif #if defined(__GLIBC__) MACHINE_API int machine_get_backtrace(void **entries, int max) { return backtrace(entries, max); } #endif #define MM_BACKTRACE_STRING_N_ENTRIES 15 __thread char backtrace_string[MM_BACKTRACE_STRING_N_ENTRIES * 40]; const char *od_alpine_warning = "WARNING: Bactrace is not supported!"; MACHINE_API const char *machine_get_backtrace_string(void) { #if defined(__GLIBC__) void *bt[MM_BACKTRACE_STRING_N_ENTRIES]; int nentries = machine_get_backtrace(bt, MM_BACKTRACE_STRING_N_ENTRIES); if (nentries <= 0) { return NULL; } char *wptr = backtrace_string; for (int i = 0; i < nentries; ++i) { wptr += sprintf(wptr, "%p ", bt[i]); } wptr += sprintf(wptr, "("); for (int i = 0; i < nentries; ++i) { void *addr = bt[i]; Dl_info info; if (dladdr(addr, &info) == 0) { wptr += sprintf(wptr, "[unknown]"); } else { void *calibrated = (void *)((uintptr_t)addr - (uintptr_t)info.dli_fbase); wptr += sprintf(wptr, "%p", calibrated); } if (i != nentries - 1) { wptr += sprintf(wptr, " "); } } wptr += sprintf(wptr, ")"); *wptr = '\0'; return backtrace_string; #endif return od_alpine_warning; } odyssey-1.6.0-rc1/sources/machinarium/benchmark/000077500000000000000000000000001526064202200216115ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/machinarium/benchmark/Makefile000066400000000000000000000013761526064202200232600ustar00rootroot00000000000000CC = gcc RM = rm CFLAGS = -I. -Wall -g -O3 -I../sources LFLAGS_LIB = ../sources/libmachinarium.a -pthread -lssl -lcrypto LFLAGS = $(LFLAGS_LIB) EXAMPLES = benchmark_csw benchmark_csw2 benchmark_channel benchmark_channel_shared benchmark_msg_alloc all: clean $(EXAMPLES) benchmark_csw: $(CC) $(CFLAGS) benchmark_csw.c $(LFLAGS) -o benchmark_csw benchmark_csw2: $(CC) $(CFLAGS) benchmark_csw2.c $(LFLAGS) -o benchmark_csw2 benchmark_channel: $(CC) $(CFLAGS) benchmark_channel.c $(LFLAGS) -o benchmark_channel benchmark_channel_shared: $(CC) $(CFLAGS) benchmark_channel_shared.c $(LFLAGS) -o benchmark_channel_shared benchmark_msg_alloc: $(CC) $(CFLAGS) benchmark_msg_alloc.c $(LFLAGS) -o benchmark_msg_alloc clean: $(RM) -f $(EXAMPLES) odyssey-1.6.0-rc1/sources/machinarium/benchmark/benchmark_channel.c000066400000000000000000000023261526064202200254020ustar00rootroot00000000000000 /* * machinarium. * * Cooperative multitasking engine. */ #include static int ops = 0; static void benchmark_reader(void *arg) { machine_channel_t *channel = arg; while (machine_active()) { machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); if (msg) { machine_msg_free(msg); } ops++; } } static void benchmark_writer(void *arg) { machine_channel_t *channel = arg; while (machine_active()) { machine_msg_t *msg; msg = machine_msg_create(0); machine_channel_write(channel, msg); ops++; machine_sleep(0); } } static void benchmark_runner(void *arg) { printf("benchmark started.\n"); machine_channel_t *channel; channel = machine_channel_create(); int r = machine_coroutine_create(benchmark_reader, channel); int w = machine_coroutine_create(benchmark_writer, channel); machine_sleep(1000); machine_stop_current(); machine_cancel(r); machine_join(r); machine_join(w); machine_channel_free(channel); printf("done.\n"); printf("channel operations %d in 1 sec.\n", ops); } int main(int argc, char *argv[]) { machinarium_init(); int id = machine_create("benchmark_channel", benchmark_runner, NULL); machine_wait(id); machinarium_free(); return 0; } odyssey-1.6.0-rc1/sources/machinarium/benchmark/benchmark_channel_shared.c000066400000000000000000000022401526064202200267230ustar00rootroot00000000000000 /* * machinarium. * * Cooperative multitasking engine. */ #include static int ops = 0; static void benchmark_reader(void *arg) { machine_channel_t *q = arg; while (machine_active()) { machine_msg_t *msg; msg = machine_channel_read(q, UINT32_MAX); if (msg) { machine_msg_free(msg); } ops++; } } static void benchmark_writer(void *arg) { machine_channel_t *q = arg; while (machine_active()) { machine_msg_t *msg; msg = machine_msg_create(0); machine_channel_write(q, msg); ops++; machine_sleep(0); } } static void benchmark_runner(void *arg) { printf("benchmark started.\n"); machine_channel_t *q; q = machine_channel_create(); int r = machine_coroutine_create(benchmark_reader, q); int w = machine_coroutine_create(benchmark_writer, q); machine_sleep(1000); machine_stop_current(); machine_cancel(r); machine_join(r); machine_join(w); machine_channel_free(q); printf("done.\n"); printf("channel operations %d in 1 sec.\n", ops); } int main(int argc, char *argv[]) { machinarium_init(); int id = machine_create("benchmark_channel", benchmark_runner, NULL); machine_wait(id); machinarium_free(); return 0; } odyssey-1.6.0-rc1/sources/machinarium/benchmark/benchmark_csw.c000066400000000000000000000015211526064202200245620ustar00rootroot00000000000000 /* * machinarium. * * Cooperative multitasking engine. */ /* * This example shows coroutine context switch (yield) * performance done in one second. */ #include static int csw = 0; static void benchmark_worker(void *arg) { printf("worker started.\n"); while (machine_active()) { csw++; machine_sleep(0); } printf("worker done.\n"); } static void benchmark_runner(void *arg) { printf("benchmark started.\n"); machine_coroutine_create(benchmark_worker, NULL); machine_sleep(1000); printf("done.\n"); printf("context switches %d in 1 sec.\n", csw); machine_stop_current(); } int main(int argc, char *argv[]) { machinarium_init(); int id = machine_create("benchmark_csw", benchmark_runner, NULL); int rc = machine_wait(id); printf("retcode from machine wait %d.\n", rc); machinarium_free(); return 0; } odyssey-1.6.0-rc1/sources/machinarium/benchmark/benchmark_csw2.c000066400000000000000000000021171526064202200246460ustar00rootroot00000000000000 /* * machinarium. * * Cooperative multitasking engine. */ /* * This example shows coroutine context switch (yield) * performance done in one second. */ #include #define MAX_COROUTINES 256 int csws[MAX_COROUTINES]; static void benchmark_worker(void *arg) { int id = arg; /* printf("worker started.\n"); */ while (machine_active()) { csws[id]++; machine_sleep(0); } /* printf("worker done.\n"); */ } static void benchmark_runner(void *arg) { printf("benchmark started.\n"); for (int i = 0; i < MAX_COROUTINES; ++i) { machine_coroutine_create(benchmark_worker, i); } machine_sleep(1000); printf("done.\n"); int csw = 0; for (int i = 0; i < MAX_COROUTINES; ++i) { csw += csws[i]; } fflush(stdout); printf("_______________________________\n"); printf("context switches %d in 1 sec.\n", csw); machine_stop_current(); } int main(int argc, char *argv[]) { machinarium_init(); int id = machine_create("benchmark_csw", benchmark_runner, NULL); int rc = machine_wait(id); printf("retcode from machine wait %d.\n", rc); machinarium_free(); return 0; } odyssey-1.6.0-rc1/sources/machinarium/benchmark/benchmark_msg_alloc.c000066400000000000000000000023701526064202200257310ustar00rootroot00000000000000 /* * machinarium. * * Cooperative multitasking engine. */ /* * This example shows coroutine context switch (yield) * performance done in one second. */ #include #define MAX_COROUTINES 256 #define ALLOC_SZ 2048 ssize_t corotine_alloced[MAX_COROUTINES]; static void benchmark_worker(void *arg) { ssize_t i = arg; machine_msg_t *msg; /* printf("worker started.\n"); */ while (machine_active()) { msg = machine_msg_create(ALLOC_SZ); corotine_alloced[i] += ALLOC_SZ; machine_sleep(0); machine_msg_free(msg); } /* printf("worker done.\n"); */ } static void benchmark_runner(void *arg) { printf("benchmark started.\n"); for (int i = 0; i < MAX_COROUTINES; ++i) { machine_coroutine_create(benchmark_worker, i); } machine_sleep(1000); printf("done.\n"); ssize_t tot = 0; for (int i = 0; i <= 1 && i < MAX_COROUTINES; ++i) { tot += corotine_alloced[i]; } fflush(stdout); printf("_______________________________\n"); printf("memory alloc %d in 1 sec.\n", tot); machine_stop_current(); } int main(int argc, char *argv[]) { machinarium_init(); int id = machine_create("benchmark_csw", benchmark_runner, NULL); int rc = machine_wait(id); printf("retcode from machine wait %d.\n", rc); machinarium_free(); return 0; } odyssey-1.6.0-rc1/sources/machinarium/bind.c000066400000000000000000000020751526064202200207430ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include MACHINE_API int mm_io_bind(mm_io_t *obj, struct sockaddr *sa, int flags) { mm_io_t *io = mm_cast(mm_io_t *, obj); mm_errno_set(0); if (io->connected) { mm_errno_set(EINPROGRESS); return -1; } int rc; rc = mm_io_socket(io, sa); if (rc == -1) { goto error; } rc = mm_socket_set_reuseaddr(io->fd, flags & MM_BINDWITH_SO_REUSEADDR); if (rc == -1) { mm_errno_set(errno); goto error; } rc = mm_socket_set_reuseport(io->fd, flags & MM_BINDWITH_SO_REUSEPORT); if (rc == -1) { mm_errno_set(errno); goto error; } if (sa->sa_family == AF_INET6) { rc = mm_socket_set_ipv6only(io->fd, 1); if (rc == -1) { mm_errno_set(errno); goto error; } } rc = mm_socket_bind(io->fd, sa); if (rc == -1) { mm_errno_set(errno); goto error; } return 0; error: if (io->fd != -1) { close(io->fd); io->fd = -1; } io->handle.fd = -1; return -1; } odyssey-1.6.0-rc1/sources/machinarium/call.c000066400000000000000000000034041526064202200207370ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include static void mm_call_timer_cb(mm_timer_t *handle) { mm_call_t *call = handle->arg; if (call->type == MM_CALL_NONE) { return; } call->timedout = 1; call->status = ETIMEDOUT; if (call->coroutine) { mm_scheduler_wakeup(&mm_self->scheduler, call->coroutine); } } static void mm_call_cancel_cb(void *obj, void *arg) { mm_call_t *call = arg; (void)obj; if (call->type == MM_CALL_NONE) { return; } call->status = ECANCELED; if (call->coroutine) { mm_scheduler_wakeup(&mm_self->scheduler, call->coroutine); } } void mm_call(mm_call_t *call, mm_calltype_t type, uint32_t time_ms) { mm_scheduler_t *scheduler; scheduler = &mm_self->scheduler; mm_clock_t *clock; clock = &mm_self->loop.clock; mm_coroutine_t *coroutine; coroutine = mm_scheduler_current(scheduler); coroutine->call_ptr = call; call->coroutine = coroutine; call->type = type; call->cancel_function = mm_call_cancel_cb; call->arg = call; call->timedout = 0; call->status = 0; if (mm_coroutine_is_cancelled(coroutine)) { call->status = ECANCELED; call->type = MM_CALL_NONE; call->timedout = 0; call->coroutine = NULL; call->cancel_function = NULL; call->arg = NULL; coroutine->call_ptr = NULL; return; } mm_timer_t timer; mm_timer_init(&timer, mm_call_timer_cb, call, time_ms); if (time_ms != UINT32_MAX) { mm_timer_start(clock, &timer); } mm_scheduler_yield(scheduler); mm_timer_stop(&timer); call->type = MM_CALL_NONE; call->coroutine = NULL; call->cancel_function = NULL; call->arg = NULL; coroutine->call_ptr = NULL; mm_errno_set(call->status); } odyssey-1.6.0-rc1/sources/machinarium/cert_hash.c000066400000000000000000000005711526064202200217660ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include MACHINE_API ssize_t machine_tls_cert_hash( mm_io_t *obj, unsigned char (*cert_hash)[MM_CERT_HASH_LEN], unsigned int *len) { mm_io_t *io = mm_cast(mm_io_t *, obj); return mm_tls_get_cert_hash(io, cert_hash, len); } odyssey-1.6.0-rc1/sources/machinarium/channel.c000066400000000000000000000114151526064202200214350ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include void mm_channel_init(mm_channel_t *channel) { mm_sleeplock_init(&channel->lock); mm_list_init(&channel->msg_list); channel->msg_list_count = 0; mm_list_init(&channel->readers); channel->readers_count = 0; } void mm_channel_free(mm_channel_t *channel) { if (channel->msg_list_count == 0) { return; } mm_list_t *i, *n; mm_list_foreach_safe (&channel->msg_list, i, n) { mm_msg_t *msg = mm_container_of(i, mm_msg_t, link); mm_msg_unref(&mm_self->msg_cache, msg); } } mm_retcode_t mm_channel_write(mm_channel_t *channel, mm_msg_t *msg) { mm_sleeplock_lock(&channel->lock); if (channel->readers_count) { mm_channelrd_t *reader; reader = mm_container_of(channel->readers.next, mm_channelrd_t, link); reader->result = msg; mm_list_unlink(&reader->link); channel->readers_count--; int event_mgr_fd; event_mgr_fd = mm_eventmgr_signal(&reader->event); mm_sleeplock_unlock(&channel->lock); if (event_mgr_fd > 0) { mm_eventmgr_wakeup(event_mgr_fd); } return MM_OK_RETCODE; } switch (channel->limit_policy) { case MM_CHANNEL_UNLIMITED: break; case MM_CHANNEL_LIMIT_HARD: { if (channel->msg_list_count >= channel->chan_limit) { machine_msg_free((machine_msg_t *)msg); mm_sleeplock_unlock(&channel->lock); return MM_NOTOK_RETCODE; } } break; case MM_CHANNEL_LIMIT_SOFT: { /* * probability of not accepting message is 0 when channel->msg_list_count < channel->chan_limit * probability of not accepting message is 1 when channel->msg_list_count >= 2 * channel->chan_limit * else uniform distribution probability * * X || (Y && Z) and eval is lazy */ if ((channel->msg_list_count >= 2 * channel->chan_limit) || ((channel->msg_list_count >= channel->chan_limit) && (machine_lrand48() % channel->chan_limit < channel->msg_list_count - channel->chan_limit))) { machine_msg_free((machine_msg_t *)msg); mm_sleeplock_unlock(&channel->lock); return MM_NOTOK_RETCODE; } } break; default: assert(0); } mm_list_append(&channel->msg_list, &msg->link); channel->msg_list_count++; mm_sleeplock_unlock(&channel->lock); return MM_OK_RETCODE; } mm_msg_t *mm_channel_read(mm_channel_t *channel, uint32_t time_ms) { /* try to get first message, if no other readers are * waiting, otherwise put reader in the wait * channel */ mm_sleeplock_lock(&channel->lock); mm_list_t *next; if ((channel->msg_list_count > 0) && (channel->readers_count == 0)) { next = mm_list_pop(&channel->msg_list); channel->msg_list_count--; mm_sleeplock_unlock(&channel->lock); return mm_container_of(next, mm_msg_t, link); } /* put reader into channel and register event */ mm_channelrd_t reader; reader.result = NULL; mm_list_init(&reader.link); mm_eventmgr_add(&mm_self->event_mgr, &reader.event); mm_list_append(&channel->readers, &reader.link); channel->readers_count++; mm_sleeplock_unlock(&channel->lock); /* wait for cancel, timedout or writer event */ mm_eventmgr_wait(&mm_self->event_mgr, &reader.event, time_ms); mm_sleeplock_lock(&channel->lock); if (!reader.result) { assert(channel->readers_count > 0); channel->readers_count--; mm_list_unlink(&reader.link); } mm_sleeplock_unlock(&channel->lock); /* timedout or cancel */ if (reader.event.call.status != 0) { if (reader.result) { mm_msg_unref(&mm_self->msg_cache, reader.result); } return NULL; } return reader.result; } mm_msg_t *mm_channel_read_back(mm_channel_t *channel, uint32_t time_ms) { /* try to get first message, if no other readers are * waiting, otherwise put reader in the wait * channel */ mm_sleeplock_lock(&channel->lock); mm_list_t *next; if ((channel->msg_list_count > 0) && (channel->readers_count == 0)) { next = mm_list_pop_back(&channel->msg_list); channel->msg_list_count--; mm_sleeplock_unlock(&channel->lock); return mm_container_of(next, mm_msg_t, link); } /* put reader into channel and register event */ mm_channelrd_t reader; reader.result = NULL; mm_list_init(&reader.link); mm_eventmgr_add(&mm_self->event_mgr, &reader.event); mm_list_append(&channel->readers, &reader.link); channel->readers_count++; mm_sleeplock_unlock(&channel->lock); /* wait for cancel, timedout or writer event */ mm_eventmgr_wait(&mm_self->event_mgr, &reader.event, time_ms); mm_sleeplock_lock(&channel->lock); if (!reader.result) { assert(channel->readers_count > 0); channel->readers_count--; mm_list_unlink(&reader.link); } mm_sleeplock_unlock(&channel->lock); /* timedout or cancel */ if (reader.event.call.status != 0) { if (reader.result) { mm_msg_unref(&mm_self->msg_cache, reader.result); } return NULL; } return reader.result; } odyssey-1.6.0-rc1/sources/machinarium/channel_api.c000066400000000000000000000035301526064202200222650ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include MACHINE_API machine_channel_t *machine_channel_create(void) { mm_channel_t *channel; channel = mm_malloc(sizeof(mm_channel_t)); if (channel == NULL) { mm_errno_set(ENOMEM); return NULL; } mm_channel_init(channel); channel->limit_policy = MM_CHANNEL_UNLIMITED; return (machine_channel_t *)channel; } MACHINE_API void machine_channel_assign_limit_policy(machine_channel_t *obj, int limit, mm_channel_limit_policy_t policy) { mm_channel_t *channel; channel = mm_cast(mm_channel_t *, obj); channel->chan_limit = limit; channel->limit_policy = policy; return; } MACHINE_API void machine_channel_free(machine_channel_t *obj) { mm_channel_t *channel; channel = mm_cast(mm_channel_t *, obj); mm_channel_free(channel); mm_free(channel); return; } MACHINE_API mm_retcode_t machine_channel_write(machine_channel_t *obj, machine_msg_t *obj_msg) { mm_channel_t *channel; channel = mm_cast(mm_channel_t *, obj); mm_msg_t *msg = mm_cast(mm_msg_t *, obj_msg); return mm_channel_write(channel, msg); } MACHINE_API machine_msg_t *machine_channel_read(machine_channel_t *obj, uint32_t time_ms) { mm_channel_t *channel; channel = mm_cast(mm_channel_t *, obj); mm_msg_t *msg; msg = mm_channel_read(channel, time_ms); return (machine_msg_t *)msg; } MACHINE_API machine_msg_t *machine_channel_read_back(machine_channel_t *obj, uint32_t time_ms) { mm_channel_t *channel; channel = mm_cast(mm_channel_t *, obj); mm_msg_t *msg; msg = mm_channel_read_back(channel, time_ms); return (machine_msg_t *)msg; } MACHINE_API size_t machine_channel_get_size(machine_channel_t *chan) { return mm_channel_get_size(mm_cast(mm_channel_t *, chan)); } odyssey-1.6.0-rc1/sources/machinarium/clock.c000066400000000000000000000044271526064202200211250ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include static int mm_clock_cmp(const mm_heap_node_t *aa, const mm_heap_node_t *bb) { const mm_timer_t *a = mm_container_of(aa, mm_timer_t, heap); const mm_timer_t *b = mm_container_of(bb, mm_timer_t, heap); if (a->timeout < b->timeout) { return 1; } if (b->timeout < a->timeout) { return 0; } return a->seq < b->seq; } void mm_clock_init(mm_clock_t *clock) { mm_heap_init(&clock->timers, mm_clock_cmp); clock->timers_seq = 0; clock->active = 0; clock->time_ms = 0; clock->time_ns = 0; clock->time_us = 0; clock->time_sec = 0; clock->time_cached = 0; } void mm_clock_free(mm_clock_t *clock) { (void)clock; } int mm_clock_timer_add(mm_clock_t *clock, mm_timer_t *timer) { timer->seq = clock->timers_seq++; timer->timeout = clock->time_ms + timer->interval; timer->active = 1; timer->clock = clock; mm_heap_push(&clock->timers, &timer->heap); return 0; } int mm_clock_timer_del(mm_clock_t *clock, mm_timer_t *timer) { if (!timer->active) { return -1; } mm_heap_remove(&clock->timers, &timer->heap); timer->active = 0; return 0; } mm_timer_t *mm_clock_timer_min(mm_clock_t *clock) { mm_heap_node_t *m = mm_heap_min(&clock->timers); if (m != NULL) { return mm_container_of(m, mm_timer_t, heap); } return NULL; } void mm_clock_step(mm_clock_t *clock) { while (1) { mm_heap_node_t *n = mm_heap_min(&clock->timers); if (n == NULL) { break; } mm_timer_t *tm = mm_container_of(n, mm_timer_t, heap); if (tm->timeout > clock->time_ms) { break; } mm_heap_pop(&clock->timers); tm->active = 0; tm->callback(tm); } } static uint64_t mm_clock_gettime(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec * (uint64_t)1e9 + t.tv_nsec; } static int64_t mm_clock_gettimeofday(void) { struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_sec; } void mm_clock_update(mm_clock_t *clock) { if (clock->time_cached) { return; } clock->time_ns = mm_clock_gettime(); clock->time_ms = clock->time_ns / 1000000; clock->time_us = clock->time_ns / 1000; clock->time_sec = mm_clock_gettimeofday(); clock->time_cached = 1; } odyssey-1.6.0-rc1/sources/machinarium/close.c000066400000000000000000000010561526064202200211320ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include int mm_io_close(mm_io_t *obj) { mm_io_t *io = mm_cast(mm_io_t *, obj); if (io->fd == -1) { mm_errno_set(EBADF); return -1; } if (io->attached) { mm_io_detach(obj); } int rc; rc = close(io->fd); if (rc == -1) { mm_errno_set(errno); } io->connected = 0; io->fd = -1; io->handle.fd = -1; io->handle.on_read = NULL; io->handle.on_write = NULL; return 0; } odyssey-1.6.0-rc1/sources/machinarium/compression.c000066400000000000000000000042751526064202200223740ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include void mm_compression_free(mm_io_t *io) { if (io->zpq_stream) { mm_zpq_free(io->zpq_stream); } } int mm_compression_writev(mm_io_t *io, struct iovec *iov, int n, size_t *processed) { int size = mm_iov_size_of(iov, n); char *buffer = mm_malloc(size); if (buffer == NULL) { errno = ENOMEM; return -1; } mm_iovcpy(buffer, iov, n); int rc; rc = mm_zpq_write(io->zpq_stream, buffer, size, processed); mm_free(buffer); return rc; } /* Returns value > 0 when there is read operation pending. */ int mm_compression_read_pending(mm_io_t *io) { return mm_zpq_buffered_rx(io->zpq_stream) || mm_zpq_deferred_rx(io->zpq_stream); } /* Returns value > 0 when there is write operation pending. */ int mm_compression_write_pending(mm_io_t *io) { return mm_zpq_buffered_tx(io->zpq_stream); } /* * If client request compression, it sends list of supported * compression algorithms - client_compression_algorithms. * Each compression algorithm is identified * by one letter ('f' - Facebook zstd, 'z' - zlib). * Return value is the compression algorithm chosen by intersection * of client and server supported compression algorithms. * If match is not found, return value is MM_ZPQ_NO_COMPRESSION */ MACHINE_API char machine_compression_choose_alg(char *client_compression_algorithms) { (void)client_compression_algorithms; /* chosen compression algorithm */ char compression_algorithm = MM_ZPQ_NO_COMPRESSION; #ifdef MM_BUILD_COMPRESSION /* machinarium supported libpq compression algorithms */ char server_compression_algorithms[MM_ZPQ_MAX_ALGORITHMS]; /* get list of compression algorithms supported by machinarium */ mm_zpq_get_supported_algorithms(server_compression_algorithms); /* intersect lists */ while (*client_compression_algorithms != '\0') { if (strchr(server_compression_algorithms, *client_compression_algorithms)) { compression_algorithm = *client_compression_algorithms; break; } client_compression_algorithms += 1; } #endif return compression_algorithm; } odyssey-1.6.0-rc1/sources/machinarium/cond.c000066400000000000000000000056631526064202200207600ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include MACHINE_API machine_cond_t *machine_cond_create(void) { mm_cond_t *cond = mm_malloc(sizeof(mm_cond_t)); if (cond == NULL) { mm_errno_set(ENOMEM); return NULL; } mm_cond_init(cond); return (machine_cond_t *)cond; } MACHINE_API void machine_cond_free(machine_cond_t *obj) { mm_free(obj); } MACHINE_API void machine_cond_propagate(machine_cond_t *obj, machine_cond_t *prop) { mm_cond_t *cond = mm_cast(mm_cond_t *, obj); mm_cond_t *propagate = mm_cast(mm_cond_t *, prop); cond->propagate = propagate; } MACHINE_API void machine_cond_signal(machine_cond_t *obj) { mm_cond_t *cond = mm_cast(mm_cond_t *, obj); mm_cond_signal(cond); } MACHINE_API int machine_cond_wait(machine_cond_t *obj, uint32_t time_ms) { mm_cond_t *cond = mm_cast(mm_cond_t *, obj); return mm_cond_wait(cond, time_ms); } static inline int already_signalled(mm_cond_t *cond, mm_cond_t **history, size_t depth, size_t max) { if (depth == max) { return 1; } for (size_t i = 0; i < depth; ++i) { if (history[i] == cond) { return 1; } } return 0; } static inline void signal_impl(mm_cond_t *cond, mm_scheduler_t *sched, int propagated, mm_cond_t **history, size_t depth, size_t max) { history[depth++] = cond; if (cond->propagate) { if (!already_signalled(cond->propagate, history, depth, max)) { signal_impl(cond->propagate, sched, 1 /* propagated */, history, depth, max); } } mm_list_t *i; mm_list_foreach (&cond->awaiters, i) { mm_cond_awaiter_t *awaiter; awaiter = mm_container_of(i, mm_cond_awaiter_t, link); awaiter->propagated = propagated; assert(awaiter->owner == sched); mm_scheduler_wakeup(sched, awaiter->call.coroutine); } } void mm_cond_propagate(mm_cond_t *src, mm_cond_t *dst) { src->propagate = dst; } void mm_cond_signal(mm_cond_t *cond) { mm_scheduler_t *sched = &mm_self->scheduler; #ifndef NDEBUG assert(cond->owner == NULL || cond->owner == sched); #endif mm_cond_t *history[32]; memset(history, 0, sizeof(history)); signal_impl(cond, sched, 0, history, 0, sizeof(history) / sizeof(history[0])); } int mm_cond_wait(mm_cond_t *cond, uint32_t time_ms) { #ifndef NDEBUG assert(cond->owner == NULL || cond->owner == &mm_self->scheduler); cond->owner = &mm_self->scheduler; #endif mm_errno_set(0); mm_cond_awaiter_t awaiter; memset(&awaiter, 0, sizeof(mm_cond_awaiter_t)); mm_list_init(&awaiter.link); #ifndef NDEBUG awaiter.owner = &mm_self->scheduler; #endif mm_list_append(&cond->awaiters, &awaiter.link); mm_call(&awaiter.call, MM_CALL_COND, time_ms); mm_list_unlink(&awaiter.link); #ifndef NDEBUG cond->owner = NULL; #endif if (awaiter.call.status != 0) { return MM_COND_WAIT_FAIL; } if (awaiter.propagated) { return MM_COND_WAIT_OK_PROPAGATED; } return MM_COND_WAIT_OK; } odyssey-1.6.0-rc1/sources/machinarium/connect.c000066400000000000000000000033121526064202200214530ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include static int mm_connect(mm_io_t *io, struct sockaddr *sa, uint32_t time_ms) { mm_machine_t *machine = mm_self; mm_errno_set(0); if (mm_call_is_active(&io->call)) { mm_errno_set(EINPROGRESS); return -1; } if (io->connected) { mm_errno_set(EINPROGRESS); return -1; } /* create socket */ int rc; rc = mm_io_socket(io, sa); if (rc == -1) { goto error; } /* start connection */ rc = mm_socket_connect(io->fd, sa); if (rc == 0) { rc = mm_io_attach(io); if (rc == -1) { goto error; } goto done; } assert(rc == -1); if (errno != EINPROGRESS) { mm_errno_set(errno); goto error; } /* add socket to event loop */ rc = mm_io_attach(io); if (rc == -1) { goto error; } /* * wait for completion */ rc = mm_io_wait(io, time_ms); if (rc == -1) { mm_io_detach((mm_io_t *)io); goto error; } rc = mm_socket_error(io->handle.fd); if (rc != 0) { mm_loop_delete(&machine->loop, &io->handle); mm_errno_set(rc); goto error; } done: assert(!io->call.timedout); io->connected = 1; return 0; error: if (io->fd != -1) { close(io->fd); io->fd = -1; } io->handle.fd = -1; io->attached = 0; return -1; } MACHINE_API int mm_io_connect(mm_io_t *obj, struct sockaddr *sa, uint32_t time_ms) { mm_io_t *io = mm_cast(mm_io_t *, obj); int rc = mm_connect(io, sa, time_ms); if (rc == -1) { return -1; } return 0; } MACHINE_API int mm_io_connected(mm_io_t *obj) { mm_io_t *io = mm_cast(mm_io_t *, obj); return io->connected; } odyssey-1.6.0-rc1/sources/machinarium/context.c000066400000000000000000000072531526064202200215160ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #ifdef USE_UCONTEXT #include #endif #ifdef HAVE_TSAN #include #endif typedef struct { mm_context_t *context_runner; mm_context_t *context; mm_context_function_t function; void *arg; } mm_runner_t; static __thread mm_runner_t runner; #ifdef HAVE_TSAN void mm_context_swap(mm_context_t *current, mm_context_t *new) { current->tsan_fiber = __tsan_get_current_fiber(); if (current->destroying) { new->exit_from = current; } __tsan_switch_to_fiber(new->tsan_fiber, 0); mm_context_swap_private(current, new); if (current->exit_from != NULL) { __tsan_destroy_fiber(current->exit_from->tsan_fiber); current->exit_from = NULL; } } #endif void mm_context_runner(void) { /* save argument */ volatile mm_runner_t runner_arg = runner; /* return to mm_context_create() */ mm_context_swap(runner_arg.context, runner_arg.context_runner); runner_arg.function(runner_arg.arg); abort(); } #ifdef USE_UCONTEXT static inline void mm_context_prepare_ucontext(ucontext_t *uctx, mm_contextstack_t *stack) { /* actually, there are no errors defined in docs */ getcontext(uctx); uctx->uc_link = NULL; uctx->uc_stack.ss_sp = stack->pointer; uctx->uc_stack.ss_size = stack->size; #ifdef __e2k__ if (makecontext_e2k(uctx, mm_context_runner, 0) != 0) { abort(); } #else makecontext(uctx, mm_context_runner, 0); #endif } #else static inline void write_func_ptr(void **dst, void (*func)(void)) { memcpy(dst, &func, sizeof(func)); } #ifdef HAVE_ASAN __attribute__((no_sanitize("address"))) static void mm_context_zero_ptrs(void **dst, int count) { for (int i = 0; i < count; i++) { dst[i] = NULL; } } #else #define mm_context_zero_ptrs(dst, count) \ memset((dst), 0, (count) * sizeof(void *)) #endif static inline void **mm_context_prepare(mm_contextstack_t *stack) { void **sp; sp = (void **)(stack->pointer + stack->size); #if __amd64 *--sp = NULL; /* eq to *--sp = (void *)mm_context_runner */ write_func_ptr(--sp, mm_context_runner); /* for x86_64 we need to place return address on stack */ sp -= 6; mm_context_zero_ptrs(sp, 6); #elif __aarch64__ /* for aarch64 we need to place return address in x30 reg */ sp -= 16; mm_context_zero_ptrs(sp, 16); /* eq to *(sp + 1) = (void *)mm_context_runner; */ write_func_ptr(sp + 1, mm_context_runner); #else *--sp = NULL; /* eq to *--sp = (void *)mm_context_runner; */ write_func_ptr(--sp, mm_context_runner); sp -= 4; mm_context_zero_ptrs(sp, 4); #endif return sp; } #endif /* USE_UCONTEXT */ void mm_context_create(mm_context_t *context, mm_contextstack_t *stack, void (*function)(void *), void *arg) { /* must be first */ mm_context_t context_runner; #ifdef HAVE_TSAN context_runner.exit_from = NULL; context_runner.destroying = 0; #endif /* prepare context runner */ runner.context_runner = &context_runner; runner.context = context; runner.function = function; runner.arg = arg; /* prepare context */ #ifdef USE_UCONTEXT mm_context_prepare_ucontext(&context->uctx, stack); #else context->sp = mm_context_prepare(stack); #endif /* USE_UCONTEXT */ #ifdef HAVE_TSAN context->tsan_fiber = __tsan_create_fiber(0); context->exit_from = NULL; context->destroying = 0; #endif /* execute runner: pass function and argument */ mm_context_swap(&context_runner, context); } #ifdef USE_UCONTEXT void mm_context_destroy(mm_context_t *ctx) { (void)ctx; #ifdef __e2k__ freecontext_e2k(&ctx->uctx); #endif } #else void mm_context_destroy(mm_context_t *ctx) { (void)ctx; /* nothing to do */ } #endif odyssey-1.6.0-rc1/sources/machinarium/context_stack.c000066400000000000000000000020101526064202200226650ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #ifdef HAVE_VALGRIND #include #endif int mm_contextstack_create(mm_contextstack_t *stack, size_t size, size_t size_guard) { char *base; base = mmap(0, size_guard + size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) { return -1; } mprotect(base, size_guard, PROT_NONE); base += size_guard; stack->pointer = base; stack->size = size; stack->size_guard = size_guard; #ifdef HAVE_VALGRIND stack->valgrind_stack = VALGRIND_STACK_REGISTER( stack->pointer, stack->pointer + stack->size); #endif return 0; } void mm_contextstack_free(mm_contextstack_t *stack) { if (stack->pointer == NULL) { return; } #ifdef HAVE_VALGRIND VALGRIND_STACK_DEREGISTER(stack->valgrind_stack); #endif char *base = stack->pointer - stack->size_guard; munmap(base, stack->size_guard + stack->size); } odyssey-1.6.0-rc1/sources/machinarium/context_swap_aarch64.S000066400000000000000000000046611526064202200240400ustar00rootroot00000000000000#if defined(__APPLE__) #define MM_SYM(name) _##name #else #define MM_SYM(name) name #endif .text .global MM_SYM(mm_context_swap_impl) MM_SYM(mm_context_swap_impl): .cfi_startproc stp x8, x16, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x8, -16 .cfi_offset x16, -8 stp x17, x18, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x17, -32 .cfi_offset x18, -24 stp x19, x20, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x19, -48 .cfi_offset x20, -40 stp x21, x22, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x21, -64 .cfi_offset x22, -56 stp x23, x24, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x23, -80 .cfi_offset x24, -72 stp x25, x26, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x25, -96 .cfi_offset x26, -88 stp x27, x28, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x27, -112 .cfi_offset x28, -104 stp x29, x30, [sp, #-16]! .cfi_adjust_cfa_offset 16 .cfi_offset x29, -128 .cfi_offset x30, -120 mov x3, sp str x3, [x0] ldr x3, [x1] mov sp, x3 .cfi_def_cfa sp, 128 .cfi_offset x29, -128 .cfi_offset x30, -120 .cfi_offset x27, -112 .cfi_offset x28, -104 .cfi_offset x25, -96 .cfi_offset x26, -88 .cfi_offset x23, -80 .cfi_offset x24, -72 .cfi_offset x21, -64 .cfi_offset x22, -56 .cfi_offset x19, -48 .cfi_offset x20, -40 .cfi_offset x17, -32 .cfi_offset x18, -24 .cfi_offset x8, -16 .cfi_offset x16, -8 ldp x29, x30, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x29 .cfi_restore x30 ldp x27, x28, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x27 .cfi_restore x28 ldp x25, x26, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x25 .cfi_restore x26 ldp x23, x24, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x23 .cfi_restore x24 ldp x21, x22, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x21 .cfi_restore x22 ldp x19, x20, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x19 .cfi_restore x20 ldp x17, x18, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x17 .cfi_restore x18 ldp x8, x16, [sp], #16 .cfi_adjust_cfa_offset -16 .cfi_restore x8 .cfi_restore x16 ret x30 .cfi_endproc #if defined(__linux__) .section .note.GNU-stack,"",@progbits #endif odyssey-1.6.0-rc1/sources/machinarium/context_swap_x32.S000066400000000000000000000004601526064202200232150ustar00rootroot00000000000000.text .globl mm_context_swap_impl .type mm_context_swap_impl, @function mm_context_swap_impl: pushl %ebp pushl %ebx pushl %esi pushl %edi movl %esp, (%eax) movl (%edx), %esp popl %edi popl %esi popl %ebx popl %ebp ret .section .note.GNU-stack,"",@progbitsodyssey-1.6.0-rc1/sources/machinarium/context_swap_x64.S000066400000000000000000000025501526064202200232240ustar00rootroot00000000000000#if defined(__APPLE__) #define MM_SYM(name) _##name #else #define MM_SYM(name) name #endif .text .globl MM_SYM(mm_context_swap_impl) #if defined(__linux__) .type mm_context_swap_impl, @function #endif MM_SYM(mm_context_swap_impl): .cfi_startproc pushq %rbp .cfi_adjust_cfa_offset 8 .cfi_offset rbp, -16 pushq %rbx .cfi_adjust_cfa_offset 8 .cfi_offset rbx, -24 pushq %r12 .cfi_adjust_cfa_offset 8 .cfi_offset r12, -32 pushq %r13 .cfi_adjust_cfa_offset 8 .cfi_offset r13, -40 pushq %r14 .cfi_adjust_cfa_offset 8 .cfi_offset r14, -48 pushq %r15 .cfi_adjust_cfa_offset 8 .cfi_offset r15, -56 movq %rsp, (%rdi) movq (%rsi), %rsp .cfi_def_cfa rsp, 56 .cfi_offset r15, -56 .cfi_offset r14, -48 .cfi_offset r13, -40 .cfi_offset r12, -32 .cfi_offset rbx, -24 .cfi_offset rbp, -16 popq %r15 .cfi_adjust_cfa_offset -8 .cfi_restore r15 popq %r14 .cfi_adjust_cfa_offset -8 .cfi_restore r14 popq %r13 .cfi_adjust_cfa_offset -8 .cfi_restore r13 popq %r12 .cfi_adjust_cfa_offset -8 .cfi_restore r12 popq %rbx .cfi_adjust_cfa_offset -8 .cfi_restore rbx popq %rbp .cfi_adjust_cfa_offset -8 .cfi_restore rbp ret .cfi_endproc #if defined(__linux__) .section .note.GNU-stack,"",@progbits #endif odyssey-1.6.0-rc1/sources/machinarium/coroutine.c000066400000000000000000000034011526064202200220300ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include void mm_coroutine_init(mm_coroutine_t *coroutine) { memset(coroutine, 0, sizeof(mm_coroutine_t)); coroutine->id = UINT64_MAX; coroutine->state = MM_CNEW; coroutine->errno_ = 0; coroutine->call_ptr = NULL; memset(coroutine->name, 0, MM_COROUTINE_MAX_NAME_LEN + 1); mm_list_init(&coroutine->joiners); mm_list_init(&coroutine->link); mm_list_init(&coroutine->link_join); #ifdef MM_MEM_PROF coroutine->allocated_bytes = 0; coroutine->freed_bytes = 0; #endif } mm_coroutine_t *mm_coroutine_allocate(int stack_size, int stack_size_guard) { mm_coroutine_t *coroutine; coroutine = mm_malloc(sizeof(mm_coroutine_t)); if (coroutine == NULL) { return NULL; } mm_coroutine_init(coroutine); int rc; rc = mm_contextstack_create(&coroutine->stack, stack_size, stack_size_guard); if (rc == -1) { mm_free(coroutine); return NULL; } return coroutine; } void mm_coroutine_free(mm_coroutine_t *coroutine) { mm_context_destroy(&coroutine->context); mm_contextstack_free(&coroutine->stack); mm_free(coroutine); } void mm_coroutine_cancel(mm_coroutine_t *coroutine) { if (coroutine->cancel) { return; } coroutine->cancel++; if (coroutine->call_ptr) { mm_call_cancel(coroutine->call_ptr, coroutine); } } void mm_coroutine_set_name(mm_coroutine_t *coro, const char *name) { if (name == NULL) { memset(coro->name, 0, MM_COROUTINE_MAX_NAME_LEN + 1); return; } stpncpy(coro->name, name, MM_COROUTINE_MAX_NAME_LEN); coro->name[MM_COROUTINE_MAX_NAME_LEN] = 0; } const char *mm_coroutine_get_name(mm_coroutine_t *coro) { return coro->name; } odyssey-1.6.0-rc1/sources/machinarium/coroutine_cache.c000066400000000000000000000036651526064202200231670ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include void mm_coroutine_cache_init(mm_coroutine_cache_t *cache, int stack_size, int stack_size_guard, int limit) { mm_list_init(&cache->list); cache->count_free = 0; cache->count_total = 0; cache->stack_size = stack_size; cache->stack_size_guard = stack_size_guard; cache->limit = limit; } void mm_coroutine_cache_free(mm_coroutine_cache_t *cache) { mm_list_t *i, *n; mm_list_foreach_safe (&cache->list, i, n) { mm_coroutine_t *coroutine; coroutine = mm_container_of(i, mm_coroutine_t, link); mm_coroutine_free(coroutine); } } void mm_coroutine_cache_stat(mm_coroutine_cache_t *cache, uint64_t *count, uint64_t *count_free) { *count = cache->count_total - cache->count_free; *count_free = cache->count_free; } mm_coroutine_t *mm_coroutine_cache_pop(mm_coroutine_cache_t *cache) { mm_coroutine_t *coroutine; if (cache->count_free > 0) { mm_list_t *first = mm_list_pop(&cache->list); cache->count_free--; coroutine = mm_container_of(first, mm_coroutine_t, link); /* just in case, already set when allocated */ coroutine->origin_cache = cache; return coroutine; } coroutine = mm_coroutine_allocate(cache->stack_size, cache->stack_size_guard); if (coroutine == NULL) { return NULL; } cache->count_total++; coroutine->origin_cache = cache; return coroutine; } void mm_coroutine_cache_push(mm_coroutine_cache_t *cache, mm_coroutine_t *coroutine) { assert(coroutine->state == MM_CFREE); if (cache->count_free >= cache->limit) { cache->count_total--; mm_coroutine_free(coroutine); return; } /* cleanup name, for next coro user it doesnt have any sense */ mm_coroutine_set_name(coroutine, NULL); mm_list_init(&coroutine->link); mm_list_append(&cache->list, &coroutine->link); cache->count_free++; } odyssey-1.6.0-rc1/sources/machinarium/dns.c000066400000000000000000000131211526064202200206050ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include static mm_addrinfo_t *addrinfo_clone(struct addrinfo *ai) { mm_addrinfo_t *head = NULL; mm_addrinfo_t *tail = NULL; while (ai) { mm_addrinfo_t *node = mm_malloc(sizeof(mm_addrinfo_t)); if (node == NULL) { goto error; } memset(node, 0, sizeof(mm_addrinfo_t)); atomic_init(&node->refs, 1); node->ai_socktype = ai->ai_socktype; node->ai_protocol = ai->ai_protocol; node->ai_flags = ai->ai_flags; node->ai_family = ai->ai_family; node->ai_addrlen = ai->ai_addrlen; if (ai->ai_addr != NULL && ai->ai_addrlen > 0) { node->ai_addr = mm_malloc(ai->ai_addrlen); if (node->ai_addr == NULL) { mm_free(node); goto error; } memcpy(node->ai_addr, ai->ai_addr, ai->ai_addrlen); } if (ai->ai_canonname != NULL) { node->ai_canonname = mm_strdup(ai->ai_canonname); if (node->ai_canonname == NULL) { mm_free(node->ai_addr); mm_free(node); goto error; } } node->ai_next = NULL; if (tail != NULL) { tail->ai_next = node; } else { head = node; } tail = node; ai = ai->ai_next; } return head; error: mm_freeaddrinfo(head); return NULL; } void mm_freeaddrinfo(mm_addrinfo_t *ai) { if (ai == NULL) { return; } uint64_t r = atomic_fetch_sub(&ai->refs, 1); if (r > 1) { return; } while (ai) { mm_addrinfo_t *next = ai->ai_next; mm_free(ai->ai_addr); mm_free(ai->ai_canonname); mm_free(ai); ai = next; } } void mm_free_dns_cache_entry(mm_dns_cache_entry_t *e) { mm_list_unlink(&e->link); if (e->ai) { mm_freeaddrinfo(e->ai); e->ai = NULL; } mm_free(e); } typedef struct { char *addr; char *service; struct addrinfo *hints; mm_addrinfo_t **res; int rc; } mm_getaddrinfo_t; static int entry_eq(const mm_getaddrinfo_t *arg, const mm_dns_cache_entry_t *e) { if (arg->hints != NULL) { if (arg->hints->ai_socktype != e->ai_socktype) { return 0; } if (arg->hints->ai_protocol != e->ai_protocol) { return 0; } if (arg->hints->ai_family != e->ai_family) { return 0; } } else { /* * from man getaddrinfo: * * Specifying hints as NULL is equivalent to setting * ai_socktype and ai_protocol to 0; ai_family to AF_UNSPEC; and ai_flags to * (AI_V4MAPPED | AI_ADDRCONFIG). */ if (e->ai_socktype != 0 || e->ai_protocol != 0 || e->ai_family != AF_UNSPEC) { return 0; } } if (arg->addr == NULL) { if (strlen(e->addr) > 0) { return 0; } } else { if (strcmp(arg->addr, e->addr) != 0) { return 0; } } if (arg->service == NULL) { if (strlen(e->service) > 0) { return 0; } } else { if (strcmp(arg->service, e->service) != 0) { return 0; } } return 1; } static void mm_getaddrinfo_cb(mm_taskmgr_worker_t *worker, void *arg) { mm_getaddrinfo_t *gai = arg; mm_dns_cache_entry_t *e = NULL; uint64_t now_ms = machine_time_ms(); mm_list_t *i, *s; mm_list_foreach_safe (&worker->dns_cache, i, s) { mm_dns_cache_entry_t *c; c = mm_container_of(i, mm_dns_cache_entry_t, link); if (entry_eq(gai, c)) { e = c; break; } if (c->valid_until_ms <= now_ms) { mm_free_dns_cache_entry(c); } } if (e != NULL && e->valid_until_ms > now_ms) { /* valid value, reuse it */ gai->rc = e->gai_rc; if (e->gai_rc == 0) { atomic_fetch_add(&e->ai->refs, 1); *gai->res = e->ai; } return; } if (e != NULL) { /* expired, free only gai result and reuse entry object */ mm_freeaddrinfo(e->ai); e->ai = NULL; } else { /* unknown key */ e = mm_malloc(sizeof(mm_dns_cache_entry_t)); if (e == NULL) { gai->rc = -1; return; } memset(e, 0, sizeof(mm_dns_cache_entry_t)); mm_list_init(&e->link); mm_list_append(&worker->dns_cache, &e->link); if (gai->addr != NULL) { strncpy(e->addr, gai->addr, sizeof(e->addr) - 1); } if (gai->service != NULL) { strncpy(e->service, gai->service, sizeof(e->service) - 1); } if (gai->hints != NULL) { e->ai_family = gai->hints->ai_family; e->ai_protocol = gai->hints->ai_protocol; e->ai_socktype = gai->hints->ai_socktype; } } struct addrinfo *ai = NULL; e->gai_rc = mm_socket_getaddrinfo(gai->addr, gai->service, gai->hints, &ai); gai->rc = e->gai_rc; e->valid_until_ms = machine_time_ms() + worker->dns_ttl_ms; if (gai->rc == 0) { mm_addrinfo_t *copy = addrinfo_clone(ai); freeaddrinfo(ai); if (copy == NULL) { e->valid_until_ms = 0; gai->rc = -1; return; } atomic_fetch_add(©->refs, 1); *gai->res = copy; e->ai = copy; } } int mm_getaddrinfo(char *addr, char *service, struct addrinfo *hints, mm_addrinfo_t **res, uint32_t time_ms) { mm_getaddrinfo_t gai = { .addr = addr, .service = service, .hints = hints, .res = res, .rc = 0 }; int rc; rc = mm_taskmgr_new(&machinarium.task_mgr, mm_getaddrinfo_cb, &gai, time_ms); if (rc == -1) { return -1; } return gai.rc; } MACHINE_API int machine_getsockname(mm_io_t *obj, struct sockaddr *sa, int *salen) { mm_io_t *io = mm_cast(mm_io_t *, obj); mm_errno_set(0); socklen_t slen = *salen; int rc = mm_socket_getsockname(io->fd, sa, &slen); if (rc < 0) { mm_errno_set(errno); return -1; } *salen = slen; return 0; } MACHINE_API int machine_getpeername(mm_io_t *obj, struct sockaddr *sa, int *salen) { mm_io_t *io = mm_cast(mm_io_t *, obj); mm_errno_set(0); socklen_t slen = *salen; int rc = mm_socket_getpeername(io->fd, sa, &slen); if (rc < 0) { mm_errno_set(errno); return -1; } *salen = slen; return 0; } odyssey-1.6.0-rc1/sources/machinarium/ds/000077500000000000000000000000001526064202200202655ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/machinarium/ds/heap.c000066400000000000000000000072231526064202200213520ustar00rootroot00000000000000/* copy-pasted from heap-inl.h of libuv */ #include #include void mm_heap_init(mm_heap_t *h, mm_heap_cmp_fn cmp) { h->min = NULL; h->cmp = cmp; h->size = 0; } mm_heap_node_t *mm_heap_min(mm_heap_t *h) { return h->min; } size_t mm_heap_size(mm_heap_t *h) { return h->size; } static void heap_swap(mm_heap_t *h, mm_heap_node_t *parent, mm_heap_node_t *child) { mm_heap_node_t *sibling = NULL; mm_heap_node_t t; t = *parent; *parent = *child; *child = t; parent->parent = child; if (child->left == child) { child->left = parent; sibling = child->right; } else { child->right = parent; sibling = child->left; } if (sibling != NULL) { sibling->parent = child; } if (parent->left != NULL) { parent->left->parent = parent; } if (parent->right != NULL) { parent->right->parent = parent; } if (child->parent == NULL) { h->min = child; } else if (child->parent->left == parent) { child->parent->left = child; } else { child->parent->right = child; } } void mm_heap_push(mm_heap_t *h, mm_heap_node_t *node) { size_t path; size_t n; size_t depth; mm_heap_node_t **parent; mm_heap_node_t **child; node->left = NULL; node->right = NULL; node->parent = NULL; path = 0; for (depth = 0, n = 1 + h->size; n >= 2; ++depth, n /= 2) { path = (path << 1) | (n & 1); } child = &h->min; parent = child; while (depth > 0) { parent = child; if (path & 1) { child = &(*child)->right; } else { child = &(*child)->left; } path >>= 1; --depth; } node->parent = *parent; *child = node; ++h->size; while (node->parent != NULL && h->cmp(node, node->parent)) { heap_swap(h, node->parent, node); } } void mm_heap_remove(mm_heap_t *h, mm_heap_node_t *node) { size_t path; size_t depth; size_t n; mm_heap_node_t *smallest; mm_heap_node_t **max; mm_heap_node_t *child; if (h->size == 0) { return; } path = 0; for (depth = 0, n = h->size; n >= 2; ++depth, n /= 2) { path = (path << 1) | (n & 1); } max = &h->min; while (depth > 0) { if (path & 1) { max = &(*max)->right; } else { max = &(*max)->left; } path >>= 1; --depth; } --h->size; child = *max; *max = NULL; if (child == node) { if (child == h->min) { h->min = NULL; } return; } child->left = node->left; child->right = node->right; child->parent = node->parent; if (child->left != NULL) { child->left->parent = child; } if (child->right != NULL) { child->right->parent = child; } if (node->parent == NULL) { h->min = child; } else if (node->parent->left == node) { node->parent->left = child; } else { node->parent->right = child; } while (1) { smallest = child; if (child->left != NULL && h->cmp(child->left, smallest)) { smallest = child->left; } if (child->right != NULL && h->cmp(child->right, smallest)) { smallest = child->right; } if (smallest == child) { break; } heap_swap(h, child, smallest); } while (child->parent != NULL && h->cmp(child, child->parent)) { heap_swap(h, child->parent, child); } } mm_heap_node_t *mm_heap_pop(mm_heap_t *h) { mm_heap_node_t *n = h->min; mm_heap_remove(h, n); return n; } static int validate_node(mm_heap_t *h, mm_heap_node_t *node, mm_heap_node_t *expected_parent) { if (node == NULL) { return 1; } if (node->parent != expected_parent) { return 0; } if (node->left != NULL && h->cmp(node->left, node)) { return 0; } if (node->right != NULL && h->cmp(node->right, node)) { return 0; } return validate_node(h, node->left, node) && validate_node(h, node->right, node); } int mm_heap_validate(mm_heap_t *h) { return validate_node(h, h->min, NULL); } odyssey-1.6.0-rc1/sources/machinarium/ds/hm.c000066400000000000000000000237251526064202200210460ustar00rootroot00000000000000#include #include #include #include #include #include #include #include static size_t adjust_to_prime(size_t sz) { static size_t primes[] = { 3, 7, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; size_t np = sizeof(primes) / sizeof(size_t); for (size_t i = 0; i < np; ++i) { if (primes[i] >= sz) { return primes[i]; } } return primes[np - 1]; } static size_t bucket_idx(mm_hashmap_t *hm, mm_hash_t hash) { return hash % hm->nbuckets; } static size_t lock_idx(mm_hashmap_t *hm, size_t bucket_idx) { return bucket_idx % hm->nlocks; } static inline void bucket_lock(mm_hashmap_bucket_t *b) { if (b->mu != NULL) { mm_mutex_lock2(b->mu); } } static inline void bucket_unlock(mm_hashmap_bucket_t *b) { if (b->mu != NULL) { mm_mutex_unlock(b->mu); } } static size_t alignsz(size_t sz) { size_t align = alignof(max_align_t); return (sz + align - 1) & ~(align - 1); } static void init_bucket(mm_hashmap_t *hm, mm_hashmap_bucket_t *b, size_t bix) { mm_list_init(&b->kvps); if (hm->nlocks > 0) { size_t lix = lock_idx(hm, bix); b->mu = &hm->locks[lix]; } else { b->mu = NULL; } } static void init_buckets(mm_hashmap_t *hm) { for (size_t i = 0; i < hm->nbuckets; ++i) { init_bucket(hm, &hm->buckets[i], i); } } static void init_locks(mm_hashmap_t *hm) { for (size_t i = 0; i < hm->nlocks; ++i) { mm_mutex_init(&hm->locks[i]); } } static void destroy_locks(mm_hashmap_t *hm) { for (size_t i = 0; i < hm->nlocks; ++i) { mm_mutex_destroy(&hm->locks[i]); } } void *mm_hashmap_kvp_key(const mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp) { return (void *)((uint8_t *)kvp + hm->keyoff); } void *mm_hashmap_kvp_val(const mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp) { return (void *)((uint8_t *)kvp + hm->valoff); } const void *mm_hashmap_kvp_key_const(const mm_hashmap_t *hm, const mm_hashmap_kvp_t *kvp) { return (const void *)((const uint8_t *)kvp + hm->keyoff); } const void *mm_hashmap_kvp_val_const(const mm_hashmap_t *hm, const mm_hashmap_kvp_t *kvp) { return (const void *)((const uint8_t *)kvp + hm->valoff); } static mm_hashmap_kvp_t *kvp_create(mm_hashmap_t *hm, mm_hash_t hash, const void *key) { mm_hashmap_kvp_t *kvp = mm_malloc(hm->kvpsize); if (kvp == NULL) { return NULL; } memset(kvp, 0, hm->kvpsize); mm_list_init(&kvp->link); kvp->hash = hash; void *kkey = mm_hashmap_kvp_key(hm, kvp); if (hm->kcopy != NULL) { int rc = hm->kcopy(kkey, key); if (rc != 0) { mm_free(kvp); return NULL; } } else { memcpy(kkey, key, hm->keysz); } return kvp; } static void kvp_free(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp) { if (hm->kdtor != NULL) { hm->kdtor(mm_hashmap_kvp_key(hm, kvp)); } if (hm->vdtor != NULL) { hm->vdtor(mm_hashmap_kvp_val(hm, kvp)); } mm_free(kvp); } mm_hashmap_t *mm_hashmap_create(size_t nbuckets, size_t nlocks, size_t keysz, size_t valsz, mm_hm_key_cmp_fn kcmp, mm_hm_key_hash_fn khash, mm_hm_key_dtor_fn kdtor, mm_hm_value_dtor_fn vdtor, mm_hm_key_copy_fn kcopy) { nbuckets = adjust_to_prime(nbuckets); if (nlocks > 1) { nlocks = adjust_to_prime(nlocks); } if (nlocks > nbuckets) { nlocks = nbuckets; } mm_hashmap_t *hm = mm_malloc(sizeof(mm_hashmap_t)); if (hm == NULL) { return NULL; } hm->buckets = mm_malloc(sizeof(mm_hashmap_bucket_t) * nbuckets); if (hm->buckets == NULL) { mm_free(hm); return NULL; } if (nlocks > 0) { hm->locks = mm_malloc(sizeof(mm_mutex_t) * nlocks); if (hm->locks == NULL) { mm_free(hm->buckets); mm_free(hm); return NULL; } } else { hm->locks = NULL; } hm->nbuckets = nbuckets; hm->nlocks = nlocks; hm->keysz = keysz; hm->valsz = valsz; hm->kcmp = kcmp; hm->khash = khash; hm->kdtor = kdtor; hm->vdtor = vdtor; hm->kcopy = kcopy; hm->keyoff = alignsz(sizeof(mm_hashmap_kvp_t)); hm->valoff = alignsz(hm->keyoff + hm->keysz); hm->kvpsize = alignsz(hm->valoff + hm->valsz); init_locks(hm); init_buckets(hm); return hm; } void mm_hashmap_clear(mm_hashmap_t *hm) { mm_list_t freelist, *i, *s; mm_list_init(&freelist); for (size_t li = 0; li < hm->nlocks; ++li) { mm_mutex_lock2(&hm->locks[li]); } for (size_t bi = 0; bi < hm->nbuckets; ++bi) { mm_hashmap_bucket_t *b = &hm->buckets[bi]; mm_list_foreach_safe (&b->kvps, i, s) { mm_hashmap_kvp_t *kvp = mm_container_of(i, mm_hashmap_kvp_t, link); mm_list_unlink(&kvp->link); mm_list_append(&freelist, &kvp->link); } } for (int li = (int)hm->nlocks - 1; li >= 0; --li) { mm_mutex_unlock(&hm->locks[li]); } mm_list_foreach_safe (&freelist, i, s) { mm_hashmap_kvp_t *kvp = mm_container_of(i, mm_hashmap_kvp_t, link); mm_list_unlink(&kvp->link); kvp_free(hm, kvp); } } void mm_hashmap_free(mm_hashmap_t *hm) { if (hm == NULL) { return; } mm_hashmap_clear(hm); destroy_locks(hm); mm_free(hm->locks); mm_free(hm->buckets); mm_free(hm); } int mm_hashmap_foreach(mm_hashmap_t *hm, mm_hm_kvp_cb_fn cb, void **argv) { int rc = 0; for (size_t bi = 0; bi < hm->nbuckets; ++bi) { mm_hashmap_bucket_t *b = &hm->buckets[bi]; bucket_lock(b); mm_list_t *i; mm_list_foreach (&b->kvps, i) { mm_hashmap_kvp_t *kvp = mm_container_of(i, mm_hashmap_kvp_t, link); rc = cb(hm, kvp, argv); if (rc != 0) { break; } } bucket_unlock(b); if (rc != 0) { break; } } return rc; } static mm_hashmap_kvp_t *kvp_find_locked(mm_hashmap_t *hm, mm_hashmap_bucket_t *bucket, mm_hash_t hash, const void *key) { mm_list_t *i; mm_list_foreach (&bucket->kvps, i) { mm_hashmap_kvp_t *kvp = mm_container_of(i, mm_hashmap_kvp_t, link); if (kvp->hash != hash) { continue; } void *kkey = mm_hashmap_kvp_key(hm, kvp); if (hm->kcmp(kkey, key) == 0) { return kvp; } } return NULL; } int mm_hashmap_lock_key(mm_hashmap_t *hm, mm_hashmap_keylock_t *klock, const void *key, int flags) { memset(klock, 0, sizeof(mm_hashmap_keylock_t)); mm_hash_t hash = hm->khash(key); size_t bidx = bucket_idx(hm, hash); mm_hashmap_bucket_t *bucket = &hm->buckets[bidx]; bucket_lock(bucket); klock->bucket = bucket; mm_hashmap_kvp_t *kvp = kvp_find_locked(hm, bucket, hash, key); if (kvp != NULL) { klock->found = 1; klock->kvp = kvp; return 0; } bucket_unlock(bucket); if (!(flags & MM_HASHMAP_CREATE)) { klock->found = 0; klock->kvp = NULL; return 0; } mm_hashmap_kvp_t *newkvp = kvp_create(hm, hash, key); if (newkvp == NULL) { return -1; } bucket_lock(bucket); kvp = kvp_find_locked(hm, bucket, hash, key); if (kvp != NULL) { kvp_free(hm, newkvp); klock->kvp = kvp; klock->found = 1; return 0; } mm_list_append(&bucket->kvps, &newkvp->link); klock->kvp = newkvp; klock->found = 0; return 0; } void mm_hashmap_unlock_key(mm_hashmap_t *hm, mm_hashmap_keylock_t *klock) { (void)hm; bucket_unlock(klock->bucket); } void mm_hashmap_remove(mm_hashmap_t *hm, mm_hashmap_keylock_t *klock) { if (klock->kvp != NULL) { mm_list_unlink(&klock->kvp->link); kvp_free(hm, klock->kvp); klock->kvp = NULL; } bucket_unlock(klock->bucket); } #define prime64_1 (0x9E3779B185EBCA87ULL) #define prime64_2 (0xC2B2AE3D27D4EB4FULL) #define prime64_3 (0x165667B19E3779F9ULL) #define prime64_4 (0x85EBCA77C2B2AE63ULL) #define prime64_5 (0x27D4EB2F165667C5ULL) static uint64_t circ_lshift(uint64_t x, int n) { return (x << n) | (x >> (64 - n)); } static inline uint64_t xxh64_round(uint64_t accn, uint64_t lanen) { accn = accn + (lanen * prime64_2); accn = circ_lshift(accn, 31); return accn * prime64_1; } static inline uint64_t xxh64_merge(uint64_t acc, uint64_t accn) { acc = acc ^ xxh64_round(0, accn); acc = acc * prime64_1; return acc + prime64_4; } static inline uint64_t read_u64(const void *b) { uint64_t v; memcpy(&v, b, sizeof(uint64_t)); return v; } static inline uint32_t read_u32(const void *b) { uint32_t v; memcpy(&v, b, sizeof(uint32_t)); return v; } mm_hash_t mm_xxh64_hash(const void *data, size_t size, uint64_t seed) { const uint8_t *p = (const uint8_t *)data; const uint8_t *end = p + size; uint64_t acc; if (size >= 4 * sizeof(uint64_t)) { uint64_t acc1, acc2, acc3, acc4; acc1 = seed + prime64_1 + prime64_2; acc2 = seed + prime64_2; acc3 = seed + 0; acc4 = seed - prime64_1; const uint8_t *limit = end - 4 * sizeof(uint64_t); while (p <= limit) { acc1 = xxh64_round(acc1, read_u64(p)); p += sizeof(uint64_t); acc2 = xxh64_round(acc2, read_u64(p)); p += sizeof(uint64_t); acc3 = xxh64_round(acc3, read_u64(p)); p += sizeof(uint64_t); acc4 = xxh64_round(acc4, read_u64(p)); p += sizeof(uint64_t); } acc = circ_lshift(acc1, 1) + circ_lshift(acc2, 7) + circ_lshift(acc3, 12) + circ_lshift(acc4, 18); acc = xxh64_merge(acc, acc1); acc = xxh64_merge(acc, acc2); acc = xxh64_merge(acc, acc3); acc = xxh64_merge(acc, acc4); } else { acc = seed + prime64_5; } acc = acc + size; while (p + sizeof(uint64_t) <= end) { acc = acc ^ xxh64_round(0, read_u64(p)); acc = circ_lshift(acc, 27) * prime64_1 + prime64_4; p += sizeof(uint64_t); } while (p + sizeof(uint32_t) <= end) { acc = acc ^ ((uint64_t)read_u32(p) * prime64_1); acc = circ_lshift(acc, 23) * prime64_2 + prime64_3; p += sizeof(uint32_t); } while (p < end) { acc = acc ^ ((uint64_t)*p * prime64_5); acc = circ_lshift(acc, 11) * prime64_1; ++p; } acc = acc ^ (acc >> 33); acc = acc * prime64_2; acc = acc ^ (acc >> 29); acc = acc * prime64_3; acc = acc ^ (acc >> 32); return acc; } odyssey-1.6.0-rc1/sources/machinarium/ds/intrusive/000077500000000000000000000000001526064202200223155ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/machinarium/ds/intrusive/lf-stack.c000066400000000000000000000045121526064202200241670ustar00rootroot00000000000000#include #include #if MM_LF_STACK_TAGGED /* ---- lock-free path: 128-bit tagged pointer via __sync builtins ---- */ static inline mm_int128 mm_lf_head_pack(mm_lf_stack_head_t h) { union { mm_lf_stack_head_t s; mm_int128 raw; } u; u.s = h; return u.raw; } static inline mm_lf_stack_head_t mm_lf_head_unpack(mm_int128 raw) { union { mm_int128 raw; mm_lf_stack_head_t s; } u; u.raw = raw; return u.s; } void mm_lf_stack_init(mm_lf_stack_t *st) { st->raw = 0; } void mm_lf_stack_destroy(mm_lf_stack_t *st) { (void)st; } int mm_lf_stack_push(mm_lf_stack_t *st, mm_lf_stack_entry_t *e) { mm_int128 old_raw = st->raw; for (;;) { mm_lf_stack_head_t old = mm_lf_head_unpack(old_raw); e->prev = (mm_lf_stack_entry_t *)old.ptr; mm_lf_stack_head_t neu; neu.ptr = (uintptr_t)e; neu.tag = old.tag + 1; mm_int128 neu_raw = mm_lf_head_pack(neu); mm_int128 prev_raw = __sync_val_compare_and_swap(&st->raw, old_raw, neu_raw); if (prev_raw == old_raw) { break; } /* CAS failed: prev_raw holds the current head. Retry. */ old_raw = prev_raw; } return 0; } mm_lf_stack_entry_t *mm_lf_stack_pop(mm_lf_stack_t *st) { mm_int128 old_raw = st->raw; for (;;) { mm_lf_stack_head_t old = mm_lf_head_unpack(old_raw); if (old.ptr == 0) { return NULL; } mm_lf_stack_entry_t *entry = (mm_lf_stack_entry_t *)old.ptr; mm_lf_stack_head_t neu; neu.ptr = (uintptr_t)entry->prev; neu.tag = old.tag + 1; mm_int128 neu_raw = mm_lf_head_pack(neu); mm_int128 prev_raw = __sync_val_compare_and_swap(&st->raw, old_raw, neu_raw); if (prev_raw == old_raw) { return entry; } /* CAS failed: retry with the current head. */ old_raw = prev_raw; } } #else /* ---- fallback path: spinlock ---- */ void mm_lf_stack_init(mm_lf_stack_t *st) { memset(st, 0, sizeof(mm_lf_stack_t)); mm_spinlock_init(&st->lock); } void mm_lf_stack_destroy(mm_lf_stack_t *st) { (void)st; } int mm_lf_stack_push(mm_lf_stack_t *st, mm_lf_stack_entry_t *e) { mm_spinlock_lock(&st->lock); e->prev = st->head; st->head = e; mm_spinlock_unlock(&st->lock); return 0; } mm_lf_stack_entry_t *mm_lf_stack_pop(mm_lf_stack_t *st) { mm_lf_stack_entry_t *e; mm_spinlock_lock(&st->lock); e = st->head; if (e) { st->head = e->prev; } mm_spinlock_unlock(&st->lock); return e; } #endif odyssey-1.6.0-rc1/sources/machinarium/ds/queue.c000066400000000000000000000057261526064202200215670ustar00rootroot00000000000000#include #include #include #include #include #include #include static uint64_t next_pow2(uint64_t n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n |= n >> 32; n++; return n; } static inline void queue_lock(mm_queue_t *q) { mm_spinlock_lock(&q->lock); } static inline void queue_unlock(mm_queue_t *q) { mm_spinlock_unlock(&q->lock); } int mm_queue_init(mm_queue_t *q, size_t capacity, size_t elsize, mm_queue_dtor_fn dtor) { if (mm_unlikely(capacity == 0)) { abort(); } memset(q, 0, sizeof(mm_queue_t)); mm_spinlock_init(&q->lock); capacity = next_pow2(capacity); q->cap = capacity; q->dtor = dtor; q->elsize = elsize; q->mask = capacity - 1; q->buf = mm_malloc(capacity * elsize); if (q->buf == NULL) { return -1; } memset(q->buf, 0, capacity * elsize); return 0; } void mm_queue_destroy(mm_queue_t *q) { if (q->dtor != NULL) { while (q->size > 0) { void *val = q->buf + (q->head * q->elsize); q->dtor(val); ++q->head; q->head &= q->mask; --q->size; } } mm_spinlock_destroy(&q->lock); mm_free(q->buf); q->buf = NULL; } int mm_queue_push(mm_queue_t *q, const void *val) { int rc = 0; queue_lock(q); if (q->size < q->cap) { void *dst = q->buf + (q->tail * q->elsize); memcpy(dst, val, q->elsize); ++q->tail; q->tail &= q->mask; ++q->size; rc = 1; } queue_unlock(q); return rc; } int mm_queue_push_extended(mm_queue_t *q, const void *val) { int rc = -1; queue_lock(q); if (q->size < q->cap) { void *dst = q->buf + (q->tail * q->elsize); memcpy(dst, val, q->elsize); ++q->tail; q->tail &= q->mask; rc = (int)(++q->size); } queue_unlock(q); return rc; } int mm_queue_pop(mm_queue_t *q, void *val) { int rc = 0; queue_lock(q); if (q->size > 0) { const void *src = q->buf + (q->head * q->elsize); memcpy(val, src, q->elsize); ++q->head; q->head &= q->mask; --q->size; rc = 1; } queue_unlock(q); return rc; } size_t mm_queue_pop_batch(mm_queue_t *q, void *dst, size_t max) { size_t total = 0; queue_lock(q); if (q->size == 0 || max == 0) { queue_unlock(q); return 0; } total = q->size; if (total > max) { total = max; } size_t to_end = q->cap - q->head; uint8_t *ptr_dst = (uint8_t *)dst; uint8_t *ptr_src = q->buf + (q->head * q->elsize); if (total <= to_end) { /* [ . . . head . . . . last . . . ] */ memcpy(ptr_dst, ptr_src, total * q->elsize); } else { /* [ . . last . . . . head . . . ] */ size_t first_part_bytes = to_end * q->elsize; size_t elements_left = total - to_end; memcpy(ptr_dst, ptr_src, first_part_bytes); memcpy(ptr_dst + first_part_bytes, q->buf, elements_left * q->elsize); } q->head = (q->head + total) & q->mask; q->size -= total; queue_unlock(q); return total; } size_t mm_queue_size(mm_queue_t *q) { queue_lock(q); size_t r = q->size; queue_unlock(q); return r; } odyssey-1.6.0-rc1/sources/machinarium/ds/vector.c000066400000000000000000000036551526064202200217440ustar00rootroot00000000000000#include #include #include #include #define MIN_CAPACITY 16 void mm_vector_init(mm_vector_t *vec, size_t elsize, mm_vector_element_dtor_fn eldtor) { memset(vec, 0, sizeof(mm_vector_t)); if (elsize == 0) { abort(); } vec->eldtor = eldtor; vec->elsize = elsize; } void mm_vector_destroy(mm_vector_t *vec) { mm_vector_clear(vec); mm_free(vec->elements); vec->capacity = 0; vec->elements = NULL; } size_t mm_vector_size(const mm_vector_t *vec) { return vec->size; } int mm_vector_empty(const mm_vector_t *vec) { return vec->size == 0; } static inline void *get_impl(mm_vector_t *vec, size_t idx) { return &vec->elements[idx * vec->elsize]; } void mm_vector_clear(mm_vector_t *vec) { if (vec->eldtor != NULL) { size_t sz = mm_vector_size(vec); for (size_t i = 0; i < sz; ++i) { vec->eldtor(get_impl(vec, i)); } } vec->size = 0; } int mm_vector_shrink_to_fit(mm_vector_t *vec) { size_t new_cap = vec->size; if (new_cap < MIN_CAPACITY) { new_cap = MIN_CAPACITY; } if (new_cap >= vec->capacity) { return 0; } void *ne = mm_realloc(vec->elements, new_cap * vec->elsize); if (ne == NULL) { return -1; } vec->elements = ne; vec->capacity = new_cap; return 0; } void *mm_vector_get(mm_vector_t *vec, size_t idx) { if (idx < vec->size) { return get_impl(vec, idx); } abort(); } void *mm_vector_back(mm_vector_t *vec) { return mm_vector_get(vec, mm_vector_size(vec) - 1); } int mm_vector_append(mm_vector_t *vec, const void *val) { if (vec->size >= vec->capacity) { size_t nc = vec->capacity == 0 ? MIN_CAPACITY : vec->capacity * 2; void *ne = mm_realloc(vec->elements, nc * vec->elsize); if (ne == NULL) { return -1; } vec->elements = ne; vec->capacity = nc; } void *n = get_impl(vec, vec->size++); if (val != NULL) { memcpy(n, val, vec->elsize); } else { memset(n, 0, vec->elsize); } return 0; } odyssey-1.6.0-rc1/sources/machinarium/ds/vrb.c000066400000000000000000000136551526064202200212340ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include static size_t align_by_pagesize(size_t capacity) { size_t page_size = sysconf(_SC_PAGESIZE); if (capacity == 0) { return page_size; } return (capacity + page_size - 1) & ~(page_size - 1); } static int mm_vrb_create_backing_fd(void) { #if defined(__linux__) int fd = memfd_create("virtual-ring-buffer", MFD_CLOEXEC); return fd; #else /* * macOS/BSD have no memfd_create(), so emulate anonymous shared memory * via shm_open() + immediate shm_unlink() (POSIX shared memory * object name is only needed to establish the mapping, unlinking * it right away makes it behave like an anonymous fd) */ char name[32]; snprintf(name, sizeof(name), "/vrb%d%x", (int)getpid(), arc4random()); int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600); if (fd < 0) { mm_errno_set(errno); return -1; } shm_unlink(name); if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { int err = errno; close(fd); mm_errno_set(err); errno = err; return -1; } return fd; #endif } mm_virtual_rbuf_t *mm_virtual_rbuf_create(size_t capacity) { mm_virtual_rbuf_t *vrb = mm_malloc(sizeof(mm_virtual_rbuf_t)); if (vrb == NULL) { mm_errno_set(ENOMEM); return NULL; } memset(vrb, 0, sizeof(mm_virtual_rbuf_t)); vrb->capacity = align_by_pagesize(capacity); int fd = mm_vrb_create_backing_fd(); if (fd < 0) { mm_errno_set(errno); mm_free(vrb); return NULL; } if (ftruncate(fd, vrb->capacity) < 0) { mm_errno_set(errno); close(fd); mm_free(vrb); return NULL; } vrb->data = mmap(NULL, 2 * vrb->capacity, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (vrb->data == MAP_FAILED) { mm_free(vrb); close(fd); return NULL; } void *original = mmap(vrb->data, vrb->capacity, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); if (original == MAP_FAILED) { munmap(vrb->data, 2 * vrb->capacity); close(fd); mm_free(vrb); return NULL; } void *mirror = mmap(vrb->data + vrb->capacity, vrb->capacity, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); if (mirror == MAP_FAILED) { munmap(vrb->data, 2 * vrb->capacity); close(fd); mm_free(vrb); return NULL; } memset(original, 0, vrb->capacity); close(fd); return vrb; } void mm_virtual_rbuf_free(mm_virtual_rbuf_t *vrb) { if (vrb->data != NULL) { munmap(vrb->data, 2 * vrb->capacity); } mm_free(vrb); } size_t mm_virtual_rbuf_capacity(const mm_virtual_rbuf_t *vrb) { return vrb->capacity; } size_t mm_virtual_rbuf_size(const mm_virtual_rbuf_t *vrb) { return vrb->wpos - vrb->rpos; } size_t mm_virtual_rbuf_free_size(const mm_virtual_rbuf_t *vrb) { return vrb->capacity - mm_virtual_rbuf_size(vrb); } struct iovec mm_virtual_rbuf_write_begin(const mm_virtual_rbuf_t *vrb) { size_t max = mm_virtual_rbuf_free_size(vrb); size_t offset = vrb->wpos % vrb->capacity; void *ptr = vrb->data + offset; return (struct iovec){ .iov_base = ptr, .iov_len = max, }; } void mm_virtual_rbuf_write_commit(mm_virtual_rbuf_t *vrb, size_t len) { size_t avail = mm_virtual_rbuf_free_size(vrb); if (mm_unlikely(len > avail)) { /* len must be taken from corresponding write_begin call */ abort(); } vrb->wpos += len; } struct iovec mm_virtual_rbuf_read_begin(const mm_virtual_rbuf_t *vrb) { size_t max = mm_virtual_rbuf_size(vrb); size_t offset = vrb->rpos % vrb->capacity; void *ptr = vrb->data + offset; return (struct iovec){ .iov_base = ptr, .iov_len = max, }; } void mm_virtual_rbuf_read_commit(mm_virtual_rbuf_t *vrb, size_t len) { size_t avail = mm_virtual_rbuf_size(vrb); if (mm_unlikely(len > avail)) { /* len must be taken from corresponding read_begin call */ abort(); } vrb->rpos += len; if (vrb->rpos == vrb->wpos) { vrb->rpos = 0; vrb->wpos = 0; } } size_t mm_virtual_rbuf_read(mm_virtual_rbuf_t *vrb, void *out, size_t count) { struct iovec vec = mm_virtual_rbuf_read_begin(vrb); if (count > vec.iov_len) { count = vec.iov_len; } memcpy(out, vec.iov_base, count); mm_virtual_rbuf_read_commit(vrb, count); return count; } size_t mm_virtual_rbuf_drain(mm_virtual_rbuf_t *vrb, size_t count) { struct iovec vec = mm_virtual_rbuf_read_begin(vrb); if (count > vec.iov_len) { count = vec.iov_len; } /* reading to nowhere */ mm_virtual_rbuf_read_commit(vrb, count); return count; } size_t mm_virtual_rbuf_write(mm_virtual_rbuf_t *vrb, const void *data, size_t count) { struct iovec vec = mm_virtual_rbuf_write_begin(vrb); if (count > vec.iov_len) { count = vec.iov_len; } memcpy(vec.iov_base, data, count); mm_virtual_rbuf_write_commit(vrb, count); return count; } int mm_virtual_rbuf_cache_init(mm_virtual_rbuf_cache_t *cache, size_t max_bufs) { memset(cache, 0, sizeof(mm_virtual_rbuf_cache_t)); cache->count = 0; cache->max = max_bufs; if (max_bufs == 0) { return 0; } cache->rbufs = mm_malloc(max_bufs * sizeof(mm_virtual_rbuf_t *)); if (cache->rbufs == NULL) { return -1; } memset(cache->rbufs, 0, max_bufs * sizeof(mm_virtual_rbuf_t *)); return 0; } void mm_virtual_rbuf_cache_destroy(mm_virtual_rbuf_cache_t *cache) { for (size_t i = 0; i < cache->count; ++i) { mm_virtual_rbuf_free(cache->rbufs[i]); } mm_free(cache->rbufs); } mm_virtual_rbuf_t *mm_virtual_rbuf_cache_get(mm_virtual_rbuf_cache_t *cache) { mm_virtual_rbuf_t *vrb = NULL; if (cache->count > 0) { --cache->count; vrb = cache->rbufs[cache->count]; cache->rbufs[cache->count] = NULL; } if (vrb != NULL) { memset(vrb->data, 0, vrb->capacity); vrb->rpos = 0; vrb->wpos = 0; } return vrb; } void mm_virtual_rbuf_cache_put(mm_virtual_rbuf_cache_t *cache, mm_virtual_rbuf_t *vrb) { if (cache->count == cache->max) { mm_virtual_rbuf_free(vrb); return; } cache->rbufs[cache->count] = vrb; cache->count++; } odyssey-1.6.0-rc1/sources/machinarium/epoll.c000066400000000000000000000113511526064202200211370ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include typedef struct mm_epoll_t mm_epoll_t; struct mm_epoll_t { mm_poll_t poll; int fd; struct epoll_event *list; int size; int count; }; static mm_poll_t *mm_epoll_create(void) { mm_epoll_t *epoll; epoll = mm_malloc(sizeof(mm_epoll_t)); if (epoll == NULL) { return NULL; } epoll->poll.iface = &mm_epoll_if; epoll->count = 0; epoll->size = 1024; int size = sizeof(struct epoll_event) * epoll->size; epoll->list = mm_malloc(size); if (epoll->list == NULL) { mm_free(epoll); return NULL; } memset(epoll->list, 0, size); epoll->fd = epoll_create1(EPOLL_CLOEXEC); if (epoll->fd == -1) { mm_free(epoll->list); mm_free(epoll); return NULL; } return &epoll->poll; } static void mm_epoll_free(mm_poll_t *poll) { mm_epoll_t *epoll = (mm_epoll_t *)poll; if (epoll->list) { mm_free(epoll->list); } mm_free(poll); } static int mm_epoll_shutdown(mm_poll_t *poll) { mm_epoll_t *epoll = (mm_epoll_t *)poll; if (epoll->fd != -1) { close(epoll->fd); epoll->fd = -1; } return 0; } static inline int mm_epoll_is_read_event(const struct epoll_event *ev) { return ev->events & EPOLLIN; } static inline int mm_epoll_is_write_event(const struct epoll_event *ev) { return ev->events & EPOLLOUT; } static inline int mm_epoll_is_err_event(const struct epoll_event *ev) { return ev->events & EPOLLERR; } static inline int mm_epoll_is_close_event(const struct epoll_event *ev) { return ev->events & EPOLLHUP || ev->events & EPOLLRDHUP; } static int mm_epoll_step(mm_poll_t *poll, int timeout) { mm_epoll_t *epoll = (mm_epoll_t *)poll; if (epoll->count == 0) { return 0; } int count; count = epoll_wait(epoll->fd, epoll->list, epoll->count, timeout); if (count <= 0) { return 0; } int i = 0; while (i < count) { struct epoll_event *ev = &epoll->list[i]; mm_fd_t *fd = ev->data.ptr; fd->last_event = 0; if (mm_epoll_is_err_event(ev) && (fd->mask & MM_ERR)) { fd->last_event |= MM_ERR; assert(fd->on_err); fd->on_err(fd); } if (mm_epoll_is_close_event(ev) && (fd->mask & MM_CLOSE)) { fd->last_event |= MM_CLOSE; assert(fd->on_close); fd->on_close(fd); } if (mm_epoll_is_read_event(ev) && (fd->mask & MM_R)) { fd->last_event |= MM_R; assert(fd->on_read); fd->on_read(fd); } if (mm_epoll_is_write_event(ev) && (fd->mask & MM_W)) { fd->last_event |= MM_W; assert(fd->on_write); fd->on_write(fd); } i++; } return count; } static inline int mm_epoll_modify(mm_poll_t *poll, mm_fd_t *fd, int mask) { if (fd->mask == mask) { return 0; } mm_epoll_t *epoll = (mm_epoll_t *)poll; struct epoll_event ev; int rc; ev.events = EPOLLET; if (mask & MM_R) { ev.events |= (EPOLLIN | EPOLLRDHUP); } if (mask & MM_W) { ev.events |= EPOLLOUT; } ev.data.ptr = fd; if (fd->mask == 0) { mm_epoll_t *epoll = (mm_epoll_t *)poll; if ((epoll->count + 1) > epoll->size) { int size = epoll->size * 2; void *ptr = mm_realloc( epoll->list, sizeof(struct epoll_event) * size); if (ptr == NULL) { return -1; } epoll->list = ptr; epoll->size = size; } rc = epoll_ctl(epoll->fd, EPOLL_CTL_ADD, fd->fd, &ev); if (rc != -1) { epoll->count++; } } else if (mask == 0) { rc = epoll_ctl(epoll->fd, EPOLL_CTL_DEL, fd->fd, &ev); if (rc != -1) { epoll->count--; } } else { rc = epoll_ctl(epoll->fd, EPOLL_CTL_MOD, fd->fd, &ev); } if (rc == -1) { return -1; } fd->mask = mask; return 0; } static int mm_epoll_add(mm_poll_t *poll, mm_fd_t *fd, mm_fd_callback_t on_read, void *on_read_arg, mm_fd_callback_t on_write, void *on_write_arg, mm_fd_callback_t on_err, void *on_err_arg, mm_fd_callback_t on_close, void *on_close_arg) { fd->on_read = on_read; fd->on_read_arg = on_read_arg; fd->on_write = on_write; fd->on_write_arg = on_write_arg; fd->on_err = on_err; fd->on_err_arg = on_err_arg; fd->on_close = on_close; fd->on_close_arg = on_close_arg; int mask = 0; if (fd->on_read != NULL) { mask |= MM_R; } if (fd->on_write != NULL) { mask |= MM_W; } if (fd->on_err != NULL) { mask |= MM_ERR; } if (fd->on_close != NULL) { mask |= MM_CLOSE; } return mm_epoll_modify(poll, fd, mask); } static int mm_epoll_del(mm_poll_t *poll, mm_fd_t *fd) { fd->on_read = NULL; fd->on_read_arg = NULL; fd->on_write = NULL; fd->on_write_arg = NULL; return mm_epoll_modify(poll, fd, 0); } mm_pollif_t mm_epoll_if = { .name = "epoll", .create = mm_epoll_create, .free = mm_epoll_free, .shutdown = mm_epoll_shutdown, .step = mm_epoll_step, .add = mm_epoll_add, .del = mm_epoll_del }; odyssey-1.6.0-rc1/sources/machinarium/event_mgr.c000066400000000000000000000070671526064202200220230ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include static void mm_eventmgr_on_read(mm_fd_t *handle) { mm_eventmgr_t *mgr = handle->on_read_arg; uint64_t id; int rc; #if defined(__linux__) rc = read(mgr->fd.fd, &id, sizeof(id)); (void)rc; assert(rc == sizeof(id)); #else /* drain pipe completely */ char buf[256]; while ((rc = read(mgr->fd.fd, buf, sizeof(buf))) > 0) { if ((size_t)rc < sizeof(buf)) { break; } } (void)id; #endif /* wakeup event waiters */ mm_sleeplock_lock(&mgr->lock); if (!mgr->count_ready) { mm_sleeplock_unlock(&mgr->lock); return; } mm_list_t *i; mm_list_foreach (&mgr->list_ready, i) { mm_event_t *event; event = mm_container_of(i, mm_event_t, link); assert(event->state == MM_EVENT_READY); event->state = MM_EVENT_ACTIVE; mm_scheduler_wakeup(&mm_self->scheduler, event->call.coroutine); } mm_list_init(&mgr->list_ready); mgr->count_ready = 0; mm_sleeplock_unlock(&mgr->lock); } int mm_eventmgr_init(mm_eventmgr_t *mgr, mm_loop_t *loop) { mm_sleeplock_init(&mgr->lock); mm_list_init(&mgr->list_ready); mm_list_init(&mgr->list_wait); mgr->count_ready = 0; mgr->count_wait = 0; memset(&mgr->fd, 0, sizeof(mgr->fd)); #if defined(__linux__) mgr->fd.fd = mm_socket_eventfd(0, NULL); #else mgr->fd.fd = mm_socket_eventfd(0, &mgr->wakeup_fd); #endif if (mgr->fd.fd == -1) { return -1; } int rc; rc = mm_loop_add_ro(loop, &mgr->fd, mm_eventmgr_on_read, mgr); if (rc == -1) { close(mgr->fd.fd); #if !defined(__linux__) close(mgr->wakeup_fd); #endif mgr->fd.fd = -1; return -1; } return 0; } void mm_eventmgr_free(mm_eventmgr_t *mgr, mm_loop_t *loop) { if (mgr->fd.fd == -1) { return; } mm_loop_delete(loop, &mgr->fd); close(mgr->fd.fd); #if !defined(__linux__) close(mgr->wakeup_fd); #endif mgr->fd.fd = -1; } void mm_eventmgr_add(mm_eventmgr_t *mgr, mm_event_t *event) { mm_list_init(&event->link); event->state = MM_EVENT_WAIT; event->event_mgr = mgr; /* add event to wait list */ mm_sleeplock_lock(&mgr->lock); mm_list_append(&mgr->list_wait, &event->link); mgr->count_wait++; mm_sleeplock_unlock(&mgr->lock); } int mm_eventmgr_wait(mm_eventmgr_t *mgr, mm_event_t *event, uint32_t time_ms) { /* wait for event */ mm_call(&event->call, MM_CALL_EVENT, time_ms); /* maybe remove from wait list */ mm_sleeplock_lock(&mgr->lock); int complete = 0; switch (event->state) { case MM_EVENT_WAIT: mm_list_unlink(&event->link); mgr->count_wait--; break; case MM_EVENT_READY: mm_list_unlink(&event->link); mgr->count_ready--; break; case MM_EVENT_ACTIVE: complete = 1; break; case MM_EVENT_NONE: assert(0); break; } event->state = MM_EVENT_NONE; mm_sleeplock_unlock(&mgr->lock); return complete; } int mm_eventmgr_signal(mm_event_t *event) { mm_eventmgr_t *mgr = event->event_mgr; mm_sleeplock_lock(&mgr->lock); if (event->state == MM_EVENT_NONE || event->state == MM_EVENT_ACTIVE) { mm_sleeplock_unlock(&mgr->lock); return 0; } int fd = 0; if (mgr->count_ready == 0) { fd = mm_eventmgr_wakeup_fd(mgr); } assert(event->state == MM_EVENT_WAIT); mm_list_unlink(&event->link); mgr->count_wait--; event->state = MM_EVENT_READY; mm_list_append(&mgr->list_ready, &event->link); mgr->count_ready++; mm_sleeplock_unlock(&mgr->lock); return fd; } void mm_eventmgr_wakeup(int fd) { uint64_t id = 1; int rc; rc = write(fd, &id, sizeof(id)); (void)rc; assert(rc == sizeof(id)); } odyssey-1.6.0-rc1/sources/machinarium/eventfd.c000066400000000000000000000061051526064202200214600ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #if defined(__linux__) #include #else #include #include #endif #include #include static void on_read_cb(mm_fd_t *handle) { mm_eventfd_t *efd = handle->on_read_arg; mm_cond_signal(&efd->cond); } int mm_eventfd_init(mm_eventfd_t *efd) { memset(efd, 0, sizeof(mm_eventfd_t)); #if defined(__linux__) efd->handle.fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (efd->handle.fd == -1) { mm_errno_set(errno); return -1; } #else int fds[2]; #if defined(__APPLE__) if (pipe(fds) == -1) { mm_errno_set(errno); return -1; } fcntl(fds[0], F_SETFD, FD_CLOEXEC); fcntl(fds[1], F_SETFD, FD_CLOEXEC); fcntl(fds[0], F_SETFL, O_NONBLOCK); fcntl(fds[1], F_SETFL, O_NONBLOCK); #else if (pipe2(fds, O_CLOEXEC | O_NONBLOCK) == -1) { mm_errno_set(errno); return -1; } #endif efd->handle.fd = fds[0]; efd->write_fd = fds[1]; #endif mm_cond_init(&efd->cond); return 0; } void mm_eventfd_destroy(mm_eventfd_t *efd) { if (efd->attached) { mm_eventfd_detach(efd); } close(efd->handle.fd); #if !defined(__linux__) close(efd->write_fd); #endif } int mm_eventfd_attach(mm_eventfd_t *efd) { mm_errno_set(0); if (efd->attached) { mm_errno_set(EINPROGRESS); return -1; } int rc; rc = mm_loop_add(&mm_self->loop, &efd->handle, on_read_cb, efd, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == -1) { mm_errno_set(errno); return -1; } efd->attached = 1; return 0; } int mm_eventfd_detach(mm_eventfd_t *efd) { mm_errno_set(0); if (!efd->attached) { mm_errno_set(ENOTCONN); return -1; } int rc; rc = mm_loop_delete(&mm_self->loop, &efd->handle); if (rc == -1) { mm_errno_set(errno); return -1; } efd->attached = 0; return 0; } int mm_eventfd_read(mm_eventfd_t *efd, uint32_t timeout_ms) { while (1) { #if defined(__linux__) uint64_t unused = 0; int rc = read(efd->handle.fd, &unused, sizeof(uint64_t)); if (rc > 0) { break; } #else char buf[256]; /* drain everything accumulated */ int rc = read(efd->handle.fd, buf, sizeof(buf)); if (rc > 0) { break; /* got at least something */ } #endif int err = errno; if (machine_errno_retryable(err)) { rc = mm_cond_wait(&efd->cond, timeout_ms); if (rc == MM_COND_WAIT_FAIL) { mm_errno_set(ETIMEDOUT); return -1; } continue; } mm_errno_set(err); return -1; } return 0; } int mm_eventfd_write(mm_eventfd_t *efd, uint32_t timeout_ms) { (void)timeout_ms; #if defined(__linux__) uint64_t value = 1; int rc = write(efd->handle.fd, &value, sizeof(uint64_t)); #else char byte = 1; int rc = write(efd->write_fd, &byte, sizeof(byte)); #endif if (rc > 0) { return 0; } int err = errno; mm_errno_set(err); return -1; } void mm_eventfd_peer_to(mm_eventfd_t *efd, mm_io_t *io) { if (efd->cond.propagate != NULL) { abort(); } mm_cond_propagate(&efd->cond, &io->cond); } void mm_eventfd_remove_peer_to(mm_eventfd_t *efd, mm_io_t *io) { if (efd->cond.propagate != &io->cond) { abort(); } mm_cond_propagate(&efd->cond, NULL); } odyssey-1.6.0-rc1/sources/machinarium/gdb/000077500000000000000000000000001526064202200204135ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/machinarium/gdb/README.md000066400000000000000000000107461526064202200217020ustar00rootroot00000000000000# GDB scripts for machinarium easy debugging ## How to use Run gdb and then initialize the script: ```bash (gdb) source ~/odyssey/sources/machinarium/gdb/machinarium-gdb.py ``` ## Commands ### info mmcoros List all machinarium coroutines for threads. Shows list of active and ready coroutines from scheduler of the thread. Current coroutine are marked by *. See `(gdb) help info mmcoros` for more. Ex: ``` (gdb) info mmcoros Thread 3 (benchmark_csw) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x555555557fe0 *1 MM_CACTIVE 0 0x555555557f70 Thread 2 (resolver: 0) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x5555555596e3 Thread 1 (benchmark_csw) machinarium coroutines: The mm_self is NULL, so no coroutines in this thread available. (gdb) info mmcoros 'benchmark_csw' Thread 3 (benchmark_csw) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x555555557fe0 *1 MM_CACTIVE 0 0x555555557f70 Thread 1 (benchmark_csw) machinarium coroutines: The mm_self is NULL, so no coroutines in this thread available. (gdb) info mmcoros 2 3 Thread 3 (benchmark_csw) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x555555557fe0 *1 MM_CACTIVE 0 0x555555557f70 Thread 2 (resolver: 0) machinarium coroutines: Id State errno Function 0 MM_CACTIVE 0 0x5555555596e3 ``` ### mmcoro Execute gdb command in context of the specified coroutine. Please note, that coroutine is determined with pair thread id and coroutine id. Also note, that in mostly cases there will be zero-frame, which is fake (this is just how gdb's unwinders works) See `help mmcoro` for more. Ex: ``` (gdb) mmcoro 2 0 bt #1 0x000055555555dd1f in mm_scheduler_yield (scheduler=0x55555557c828) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:166 #2 0x000055555555e106 in mm_call (call=0x7ffff7fb5e08, type=MM_CALL_EVENT, time_ms=4294967295) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/call.c:60 #3 0x000055555555ef3c in mm_eventmgr_wait (mgr=0x55555557c928, event=0x7ffff7fb5e00, time_ms=4294967295) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/event_mgr.c:101 #4 0x000055555555fc76 in mm_channel_read (channel=0x5555555670f0 , time_ms=4294967295) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/channel.c:117 #5 0x00005555555598cf in mm_taskmgr_main (arg=0x0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/task_mgr.c:20 #6 0x000055555555d6fa in mm_scheduler_main (arg=0x7ffff0000b70) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:17 #7 0x00005555555609ab in mm_context_runner () at /home/rkhapov/odyssey/build/third_party/machinarium/sources/context.c:28 #8 0x0000000000000000 in ?? () (gdb) mmcoro 4 0 frame apply 7 info locals Please be careful with analyzing for the frame with zero index Unless your command is not bt, the zero frame will be printed, and this frame is fake. #0 mm_clock_cmp (a=0x0, b=0x0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/clock.c:13 __PRETTY_FUNCTION__ = "mm_clock_cmp" #1 0x000055555555dd1f in mm_scheduler_yield (scheduler=0x55555557d898) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:166 current = 0x7fffe0000b70 resume = 0x55555557d8a0 __PRETTY_FUNCTION__ = "mm_scheduler_yield" #2 0x000055555555e106 in mm_call (call=0x7ffff7ef4ec0, type=MM_CALL_SLEEP, time_ms=0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/call.c:60 scheduler = 0x55555557d898 clock = 0x55555557da50 coroutine = 0x7fffe0000b70 #3 0x0000555555558a03 in machine_sleep (time_ms=0) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/machine.c:201 call = {type = MM_CALL_SLEEP, coroutine = 0x7fffe0000b70, timer = {active = 1, timeout = 181455347, interval = 0, seq = 248, callback = 0x55555555dece , arg = 0x7ffff7ef4ec0, clock = 0x55555557da50}, cancel_function = 0x55555555df3f , arg = 0x7ffff7ef4ec0, data = 0x0, timedout = 0, status = 0} #4 0x00005555555580b7 in do_smt2 (arg=0x0) at benchmark_csw.c:71 c = 103 'g' #5 0x000055555555810e in do_smt (arg=0x0) at benchmark_csw.c:79 No locals. #6 0x000055555555d6fa in mm_scheduler_main (arg=0x7fffe0000b70) at /home/rkhapov/odyssey/build/third_party/machinarium/sources/scheduler.c:17 coroutine = 0x7fffe0000b70 scheduler = 0x55555557d898 i = 0x0 ```odyssey-1.6.0-rc1/sources/machinarium/gdb/machinarium-gdb.py000066400000000000000000001171521526064202200240230ustar00rootroot00000000000000import gdb import gdb.unwinder from contextlib import contextmanager gdb.write('Loading machinarium runtime support for gdb...', stream=gdb.STDLOG) # see list.h MM_LIST_NEXT_FIELD_NAME = "next" # see context.h MM_CONTEXT_SP_FIELD_NAME = "sp" # see coroutine.h MM_COROUTINE_TYPE_NAME = "struct mm_coroutine" MM_COROUTINE_LINK_FIELD_NAME = "link" MM_COROUTINE_ID_FIELD_NAME = "id" MM_COROUTINE_FUNCTION_FIELD_NAME = "function" MM_COROUTINE_FUNCTION_ARG_NAME = "function_arg" MM_COROUTINE_STATE_FIELD_NAME = "state" MM_COROUTINE_ERRNO_FIELD_NAME = "errno_" MM_COROUTINE_CONTEXT_FIELD_NAME = "context" MM_COROUTINE_NAME_FIELD_NAME = "name" MM_COROUTINE_ALLOCATED_BYTES_FIELD_NAME = "allocated_bytes" MM_COROUTINE_FREED_BYTES_FIELD_NAME = "freed_bytes" # see machine.c MM_SELF_VARIABLE_NAME = "mm_self" # see machine.h MM_MACHINE_SCHEDULER_FIELD_NAME = "scheduler" # see scheduler.h MM_SCHEDULER_CURRENT_FIELD_NAME = "current" MM_SCHEDULER_COUNT_READY_FIELD_NAME = "count_ready" MM_SCHEDULER_COUNT_ACTIVE_FIELD_NAME = "count_active" MM_SCHEDULER_LIST_READY_FIELD_NAME = "list_ready" MM_SCHEDULER_LIST_ACTIVE_FIELD_NAME = "list_active" # see hm.h MM_HASHMAP_TYPE_NAME = "mm_hashmap_t" MM_HASHMAP_KVP_TYPE_NAME = "mm_hashmap_kvp_t" MM_HASHMAP_BUCKET_TYPE_NAME = "mm_hashmap_bucket_t" MM_HASHMAP_KVP_LINK_FIELD_NAME = "link" MM_HASHMAP_KVP_HASH_FIELD_NAME = "hash" # see pstmt.h OD_PSTMT_TYPE_NAME = "od_pstmt_t" OD_PSTMT_DESC_TYPE_NAME = "od_pstmt_desc_t" OD_PSTMT_DESC_DATA_FIELD_NAME = "data" OD_PSTMT_DESC_LEN_FIELD_NAME = "len" OD_PSTMT_DESC_FIELD_NAME = "desc" OD_PSTMT_NAME_FIELD_NAME = "name" OD_PSTMT_REFS_FIELD_NAME = "refs" OD_CLIENT_SERVER_FIELD_NAME = "server" OD_SERVER_ID_FIELD_NAME = "id" OD_SERVER_STATE_FIELD_NAME = "state" GDB_CHAR_POINTER_TYPE = gdb.lookup_type("char").pointer() GDB_MM_COROUTINE_TYPE = gdb.lookup_type(MM_COROUTINE_TYPE_NAME) GDB_MM_COROUTINE_POINTER_TYPE = GDB_MM_COROUTINE_TYPE.pointer() GDB_OD_CLIENT_PTR_TYPE = gdb.lookup_type("od_client_t").pointer() GDB_OD_SERVER_PTR_TYPE = gdb.lookup_type("od_server_t").pointer() GDB_OD_LIST_TYPE = gdb.lookup_type('od_list_t') GDB_OD_LIST_POINTER_TYPE = GDB_OD_LIST_TYPE.pointer() GDB_MM_HASHMAP_TYPE = gdb.lookup_type(MM_HASHMAP_TYPE_NAME) GDB_MM_HASHMAP_POINTER_TYPE = GDB_MM_HASHMAP_TYPE.pointer() GDB_MM_HASHMAP_KVP_TYPE = gdb.lookup_type(MM_HASHMAP_KVP_TYPE_NAME) GDB_MM_HASHMAP_KVP_POINTER_TYPE = GDB_MM_HASHMAP_KVP_TYPE.pointer() GDB_MM_HASHMAP_BUCKET_TYPE = gdb.lookup_type(MM_HASHMAP_BUCKET_TYPE_NAME) GDB_OD_PSTMT_TYPE = gdb.lookup_type(OD_PSTMT_TYPE_NAME) GDB_OD_PSTMT_POINTER_TYPE = GDB_OD_PSTMT_TYPE.pointer() GDB_OD_PSTMT_DESC_TYPE = gdb.lookup_type(OD_PSTMT_DESC_TYPE_NAME) GDB_UINT64_TYPE = gdb.lookup_type('uint64_t') GDB_SIZE_T_TYPE = gdb.lookup_type('size_t') def parse_int_or_none(s): try: return int(s) except: return None def get_mm_self_or_none(): try: return gdb.parse_and_eval(MM_SELF_VARIABLE_NAME) except gdb.error: return None def mm_get_field_offset(element_type, field_name): for f in element_type.fields(): if f.name == field_name: return f.bitpos // 8 raise KeyError( f'{field_name} field not found in type {element_type}') def get_mm_coroutine_link_offset(): return mm_get_field_offset(GDB_MM_COROUTINE_TYPE, MM_COROUTINE_LINK_FIELD_NAME) MM_COROUTINE_LINK_FIELD_OFFSET = get_mm_coroutine_link_offset() def gdb_get_current_platform(): try: return gdb.selected_frame().architecture().name() except gdb.error: return None @contextmanager def gdb_thread_restore(): try: current_thread = gdb.selected_thread() yield current_thread finally: if current_thread is not None: current_thread.switch() @contextmanager def gdb_frame_restore(): try: current_frame = gdb.selected_frame() yield current_frame finally: if current_frame is not None: current_frame.select() def mm_iterate_coroutines_list(coroutines_list, count): coros = [] if count == 0: return coros next_list_node_ptr = coroutines_list for _ in range(count): next_list_node_ptr = next_list_node_ptr[MM_LIST_NEXT_FIELD_NAME] # The actual coroutine pointer is behind of link field offset from mm_coroutine type # So we must perform actions like in mm_container_of nlnp_as_char_ptr = next_list_node_ptr.cast(GDB_CHAR_POINTER_TYPE) next_coroutine_char_ptr = nlnp_as_char_ptr - MM_COROUTINE_LINK_FIELD_OFFSET next_coroutine_ptr = next_coroutine_char_ptr.cast( GDB_MM_COROUTINE_POINTER_TYPE ) next_coroutine = next_coroutine_ptr.dereference() coros.append(next_coroutine) return coros def mm_get_current_thread_coroutine_id(): mm_self_ptr = get_mm_self_or_none() if mm_self_ptr is None or mm_self_ptr == 0: return None mm_self_val = mm_self_ptr.dereference() scheduler = mm_self_val[MM_MACHINE_SCHEDULER_FIELD_NAME] current_coroutine_ptr = scheduler[MM_SCHEDULER_CURRENT_FIELD_NAME] current_coroutine_id = None if current_coroutine_ptr != 0: current_coroutine = current_coroutine_ptr.dereference() current_coroutine_id = current_coroutine[MM_COROUTINE_ID_FIELD_NAME] return current_coroutine_id def mm_current_thread_coroutines(fn_name_filter: str = ''): mm_self_ptr = get_mm_self_or_none() if mm_self_ptr is None or mm_self_ptr == 0: return [] mm_self_val = mm_self_ptr.dereference() scheduler = mm_self_val[MM_MACHINE_SCHEDULER_FIELD_NAME] count_ready = scheduler[MM_SCHEDULER_COUNT_READY_FIELD_NAME] count_active = scheduler[MM_SCHEDULER_COUNT_ACTIVE_FIELD_NAME] list_active = scheduler[MM_SCHEDULER_LIST_ACTIVE_FIELD_NAME] list_ready = scheduler[MM_SCHEDULER_LIST_READY_FIELD_NAME] active_coroutines = mm_iterate_coroutines_list(list_active, count_active) ready_coroutines = mm_iterate_coroutines_list(list_ready, count_ready) all_coros = active_coroutines + ready_coroutines if len(fn_name_filter) > 0: filtered = [] fn = gdb.parse_and_eval(f'&{fn_name_filter}') for c in all_coros: if fn == c[MM_COROUTINE_FUNCTION_FIELD_NAME]: filtered.append(c) return filtered return all_coros def mm_find_thread(thread_id): thread = None for thr in gdb.selected_inferior().threads(): if thr.name == thread_id or str(thr.num) == thread_id: thread = thr break return thread def mm_find_coroutine_in_current_thread(target_coro_id): for coro in mm_current_thread_coroutines(): coro_id = coro[MM_COROUTINE_ID_FIELD_NAME] if target_coro_id == int(coro_id): return coro return None def mm_get_thread_coroutines(thread): thread.switch() mm_self_ptr = get_mm_self_or_none() if mm_self_ptr is None: return [] if mm_self_ptr == 0: return [] return mm_current_thread_coroutines() def format_bytes(bytes_count): if bytes_count < 1024: return f'{bytes_count}B' elif bytes_count < 1024 * 1024: return f'{bytes_count / 1024:.2f}KB' elif bytes_count < 1024 * 1024 * 1024: return f'{bytes_count / (1024 * 1024):.2f}MB' else: return f'{bytes_count / (1024 * 1024 * 1024):.2f}GB' def mm_get_context_registers_for_coroutine_x64(coroutine: gdb.Value): context = coroutine[MM_COROUTINE_CONTEXT_FIELD_NAME] raw_sp = context[MM_CONTEXT_SP_FIELD_NAME] # Some magic needs to be performed, lets see # There is the stack (raw_sp) 'inside' mm_context_switch: # # low addr | r15 <- raw_sp # | r14 # | r13 # | r12 # | rbx # | rbp # | [return address] <- desired pc # high addr| [frame of the coro] <- desired sp # # So, to 'restore' sp we need skip saved registers and return address # And to 'restore' pc we need to set it by return address reg_ptr = raw_sp r15 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 r14 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 r13 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 r12 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 rbx = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 rbp = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 rip = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 rsp = reg_ptr return { 'rsp': rsp, 'rip': rip, 'r15': r15, 'r14': r14, 'r13': r13, 'r12': r12, 'rbx': rbx, 'rbp': rbp, } def mm_get_context_registers_for_coroutine_aarch64(coroutine: gdb.Value): context = coroutine[MM_COROUTINE_CONTEXT_FIELD_NAME] raw_sp = context[MM_CONTEXT_SP_FIELD_NAME] # There is the stack (raw_sp) 'inside' mm_context_switch. # See context_swap_aarch64.S: registers are saved in pairs with # stp Xn, Xm, [sp, #-16]! (pre-decrement). The stack is LIFO, # so the LAST pair pushed (x29, x30) ends up at the LOWEST address. # # low addr | x29 (fp) <- raw_sp # | x30 (lr) <- desired pc # | x27 # | x28 # | x25 # | x26 # | x23 # | x24 # | x21 # | x22 # | x19 # | x20 # | x17 # | x18 # | x8 # | x16 # high addr| [frame of the coro] <- desired sp reg_ptr = raw_sp x29 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x30 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x27 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x28 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x25 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x26 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x23 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x24 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x21 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x22 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x19 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x20 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x17 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x18 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x8 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 x16 = gdb.parse_and_eval(f'(uint64_t*)({reg_ptr})').dereference() reg_ptr += 1 sp = reg_ptr return { 'sp': sp, 'pc': x30, 'x8': x8, 'x16': x16, 'x17': x17, 'x18': x18, 'x19': x19, 'x20': x20, 'x21': x21, 'x22': x22, 'x23': x23, 'x24': x24, 'x25': x25, 'x26': x26, 'x27': x27, 'x28': x28, 'x29': x29, 'x30': x30, } def mm_get_context_registers_for_coroutine(coroutine: gdb.Value): platform = gdb_get_current_platform() if platform == 'i386:x86-64': return mm_get_context_registers_for_coroutine_x64(coroutine) if platform and platform.startswith('aarch64'): return mm_get_context_registers_for_coroutine_aarch64(coroutine) raise gdb.error(f'unsupported platform: {platform}') class MMFrameId: def __init__(self, sp: gdb.Value, pc: gdb.Value): self.sp = sp self.pc = pc class MMContextSelector(gdb.unwinder.Unwinder): def __init__(self) -> None: super().__init__("mm-unwinder") self.registers = None gdb.invalidate_cached_frames() def target_to(self, registers) -> None: self.registers = registers def __call__(self, pending_frame: gdb.PendingFrame) -> gdb.UnwindInfo: if self.registers is None: return None sp = self.registers.get('rsp', self.registers.get('sp')) pc = self.registers.get('rip', self.registers.get('pc')) unwind_info = pending_frame.create_unwind_info(MMFrameId(sp, pc)) for reg in self.registers: unwind_info.add_saved_register(reg, self.registers[reg]) self.registers = None return unwind_info mm_context_selector = MMContextSelector() gdb.unwinder.register_unwinder(None, mm_context_selector, replace=True) class MMCoroutiesFrameFilter: def __init__(self) -> None: self.name = "mm-coroutines-frame-filter" self.enabled = False self.priority = 100 gdb.frame_filters[self.name] = self def filter(self, iters): p = list(iters) if len(p) <= 1: return p return p[1:] @contextmanager def enabled_filter(self): try: self.enabled = True yield finally: self.enabled = False mm_first_frame_skip = MMCoroutiesFrameFilter() class MMCoroutines(gdb.Command): """List all coroutines. Usage: info mmcoros - list coroutines for all threads info mmcoros - list coroutines for specified thread(s). can be thread id or name. Examples: (gdb) info mmcoros (gdb) info mmcoros thread-name1 42 (gdb) info mmcoros 41 42 43 (gdb) info mmcoros thread-name1 thread-name2 """ def __init__(self): super(MMCoroutines, self).__init__( "info mmcoros", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def _get_threads_list_from_args(self, args): ids = set(gdb.string_to_argv(args)) if len(ids) == 0: return gdb.selected_inferior().threads() threads_list = [] for thr in gdb.selected_inferior().threads(): if thr.name in ids or str(thr.num) in ids: threads_list.append(thr) return threads_list def _print_coroutines_list(self, coroutines, current_coroutine_id): gdb.write(" Id\tState\t\terrno\tFunction\tArg\tName\tMemstat\n") for coro in coroutines: coro_id = coro[MM_COROUTINE_ID_FIELD_NAME] coro_state = coro[MM_COROUTINE_STATE_FIELD_NAME] coro_errno = coro[MM_COROUTINE_ERRNO_FIELD_NAME] coro_func = coro[MM_COROUTINE_FUNCTION_FIELD_NAME] coro_arg = coro[MM_COROUTINE_FUNCTION_ARG_NAME] coro_name = coro[MM_COROUTINE_NAME_FIELD_NAME] current_coro_pref = ' ' if coro_id != current_coroutine_id else '*' try: coro_allocated = int(coro[MM_COROUTINE_ALLOCATED_BYTES_FIELD_NAME]) coro_freed = int(coro[MM_COROUTINE_FREED_BYTES_FIELD_NAME]) coro_mem_used = coro_allocated - coro_freed memstat = f'{format_bytes(coro_allocated)}/{format_bytes(coro_freed)}/{format_bytes(coro_mem_used)}' except (gdb.error, KeyError): memstat = '?/?/?' gdb.write( f'{current_coro_pref}{coro_id}\t{coro_state}\t{coro_errno}\t{coro_func}\t{coro_arg}\t{coro_name}\t{memstat}\n') def _list_coroutines_for_thread(self, thread): thread.switch() gdb.write( f"Thread {thread.num} ({thread.name}) machinarium coroutines:\n") mm_self_ptr = get_mm_self_or_none() if mm_self_ptr is None: gdb.write( f" There is no {MM_SELF_VARIABLE_NAME} in the current context. Does the executable actually use the machinarium framework?\n") return if mm_self_ptr == 0: gdb.write( f" The {MM_SELF_VARIABLE_NAME} is NULL, so no coroutines in this thread available.\n") return coroutines = mm_current_thread_coroutines() current_coroutine_id = mm_get_current_thread_coroutine_id() self._print_coroutines_list(coroutines, current_coroutine_id) def invoke(self, args, is_tty): with gdb_thread_restore(): for thread in self._get_threads_list_from_args(args): self._list_coroutines_for_thread(thread) gdb.write("\n") class MMCoroutineCmd(gdb.Command): """Execute gdb command in the context of machinarium coroutine. Or just show the object of it. Please note that coroutine is determined by the pair of thread name or thread id and coroutine id. Usage: (gdb) mmcoro Example: (gdb) mmcoro thread-name 42 (gdb) mmcoro thread-name 42 info stack (gdb) mmcoro all info stack """ def __init__(self): super(MMCoroutineCmd, self).__init__( "mmcoro", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def _parse_specified_coro_args(self, args): thread_id, coro_id, *gdbcmd = gdb.string_to_argv(args) return thread_id, parse_int_or_none(coro_id), ' '.join(gdbcmd) def _parse_args(self, args): if len(args) == 0: raise gdb.error('expected arguments, see info mmcoro for more') if args[0] == 'all' or args[0] == 'a': thread_coroutines_list = [] for th in gdb.selected_inferior().threads(): coros = mm_get_thread_coroutines(th) thread_coroutines_list.append((th, coros)) return thread_coroutines_list, ' '.join(gdb.string_to_argv(args)[1:]) thread_id, coro_id, gdbcmd = self._parse_specified_coro_args(args) thread = mm_find_thread(thread_id) if thread is None: gdb.write( f"There is no thread '{thread_id}'\n") return [], None thread.switch() coroutine = mm_find_coroutine_in_current_thread(coro_id) if coroutine is None: gdb.write( f"There is no coroutine {coro_id}\n") return [], None return [(thread, (coroutine, ))], gdbcmd def _execute_in_coroutine_context(self, thread: gdb.InferiorThread, coroutine: gdb.Value, gdbcmd: str) -> None: if len(gdbcmd) == 0: gdb.execute( f'print *({MM_COROUTINE_TYPE_NAME}*){coroutine.address}' ) return thread.switch() gdb.write( f'Coroutine {int(coroutine[MM_COROUTINE_ID_FIELD_NAME])}:\n' ) # There is no need to change context for current coroutines - it is already # equal to the context of current frame # More over, there is no way to change context for current coroutines # because of the coroutine->context is not valid for currently running coros # (sp may be change because of functions call) current_coroutine_id = mm_get_current_thread_coroutine_id() if coroutine[MM_COROUTINE_ID_FIELD_NAME] == current_coroutine_id: with gdb_frame_restore(): gdb.execute(gdbcmd) return # To switch to od_auth_frontend the coroutine context, we can use the frame unwider. # However, this will cause the first frame to be the frame of the current thread. # And then we unwides to the coroutine context. # Therefore, we should use a frame filter to skip the first frame. try: with mm_first_frame_skip.enabled_filter(): regs = mm_get_context_registers_for_coroutine(coroutine) mm_context_selector.target_to(regs) gdb.invalidate_cached_frames() gdb.execute(gdbcmd) finally: gdb.invalidate_cached_frames() def invoke(self, args, is_tty): platform = gdb_get_current_platform() if platform != 'i386:x86-64' and not (platform and platform.startswith('aarch64')): gdb.write( f"!!! Warning: current platform ({platform}) is not supported for this command !!!\n" ) with gdb_thread_restore(): gdb.write( f"Please be careful with analyzing for the frame with zero index\n" ) gdb.write( f"Unless your command is not bt, the zero frame will be printed, and this frame is fake.\n" ) gdb.write("\n") thread_coroutines_list, gdbcmd = self._parse_args(args) for th_coros in thread_coroutines_list: thread, coroutines = th_coros[0], th_coros[1] gdb.write(f"Thread {thread.num} ({thread.name}) machinarium coroutines execution:\n") for coro in coroutines: self._execute_in_coroutine_context(thread, coro, gdbcmd) class IgnoreErrorsCmd (gdb.Command): """Execute a single command, ignoring all errors. Only one-line commands are supported. This is primarily useful in scripts.""" def __init__(self): super(IgnoreErrorsCmd, self).__init__("ignore-errors", gdb.COMMAND_OBSCURE, # FIXME... gdb.COMPLETE_COMMAND) def invoke(self, arg, from_tty): try: gdb.execute(arg, from_tty) except: pass class ODGetFieldOffset(gdb.Command): """Print field offset in struct. Usage: od-get-field-offsset Examples: (gdb) od-get-field-offsset od_rule_storage_t link """ def __init__(self): super(ODGetFieldOffset, self).__init__( "od-get-field-offsset", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def invoke(self, args, _): argv = gdb.string_to_argv(args) if len(argv) != 2: gdb.write( 'Expected 2 arguments: list element type and link field name\n', stream=gdb.STDLOG) return element_type, link_field_name = argv gdb.write( f'{mm_get_field_offset(gdb.lookup_type(element_type), link_field_name)}\n', stream=gdb.STDLOG) def _od_list_iterate(list_addr, element_type, link_field_name, action): element_type = gdb.lookup_type(element_type) element_ptr_type = element_type.pointer() link_field_offset = mm_get_field_offset(element_type, link_field_name) list_addr = gdb.parse_and_eval( f'{list_addr}').cast(GDB_OD_LIST_POINTER_TYPE) list_val = list_addr.dereference() # like od_list_foreach iterator = list_val[MM_LIST_NEXT_FIELD_NAME] while iterator != list_addr: iterator_as_char_ptr = iterator.cast(GDB_CHAR_POINTER_TYPE) element_ptr = (iterator_as_char_ptr - link_field_offset).cast(element_ptr_type) element_val = element_ptr.dereference() action(element_type, element_ptr, element_val) iterator = iterator[MM_LIST_NEXT_FIELD_NAME] class ODListPrint(gdb.Command): """Print content of the odyssey list. Usage: od-list-print Examples: (gdb) od-list-print &rules->storages od_rule_storage_t link """ def __init__(self): super(ODListPrint, self).__init__( "od-list-print", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def invoke(self, args, _): argv = gdb.string_to_argv(args) if len(argv) != 3: gdb.write( 'Expected 3 arguments: od_list_t address, list element type and link field name\n', stream=gdb.STDLOG) return list_addr, element_type, link_field_name = argv total = 0 def print_element(element_type, element_ptr, element_val): nonlocal total total += 1 gdb.write(f"({element_type}*){element_ptr}: {element_val}\n\n", stream=gdb.STDLOG) _od_list_iterate(list_addr, element_type, link_field_name, print_element) gdb.write(f"Total elements in list: {total}\n", stream=gdb.STDLOG) class ODListPrintSelect(gdb.Command): """Print content of the odyssey list with field selection. Usage: od-list-print-select Examples: (gdb) od-list-print-select &rules->storages od_rule_storage_t link name """ def __init__(self): super(ODListPrintSelect, self).__init__( "od-list-print-select", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def invoke(self, args, _): argv = gdb.string_to_argv(args) if len(argv) != 4: gdb.write( 'Expected 4 arguments: od_list_t address, list element type, link field name and selected field name\n', stream=gdb.STDLOG) return list_addr, element_type, link_field_name, select_field = argv total = 0 def print_element(element_type, element_ptr, element_val): nonlocal total total += 1 gdb.write(f"(({element_type}*){element_ptr})->{select_field}: {element_val[select_field]}\n\n", stream=gdb.STDLOG) _od_list_iterate(list_addr, element_type, link_field_name, print_element) gdb.write(f"Total elements in list: {total}\n", stream=gdb.STDLOG) class ODClientCoroutines(gdb.Command): """List all client coroutines. Usage: info clients - list client coroutines for all threads info clients - list client coroutines for specified thread(s). can be thread id or name. Examples: (gdb) info clients (gdb) info clients thread-name1 42 (gdb) info clients 41 42 43 (gdb) info clients thread-name1 thread-name2 """ def __init__(self): super(ODClientCoroutines, self).__init__( "info clients", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def _get_threads_list_from_args(self, args): ids = set(gdb.string_to_argv(args)) if len(ids) == 0: return gdb.selected_inferior().threads() threads_list = [] for thr in gdb.selected_inferior().threads(): if thr.name in ids or str(thr.num) in ids: threads_list.append(thr) return threads_list def _print_coroutines_list(self, coroutines, current_coroutine_id): gdb.write(" Id\tState\t\terrno\tFunction\tArg\tName\tMemstat\n") for coro in coroutines: coro_id = coro[MM_COROUTINE_ID_FIELD_NAME] coro_state = coro[MM_COROUTINE_STATE_FIELD_NAME] coro_errno = coro[MM_COROUTINE_ERRNO_FIELD_NAME] coro_func = coro[MM_COROUTINE_FUNCTION_FIELD_NAME] coro_arg = coro[MM_COROUTINE_FUNCTION_ARG_NAME] coro_name = coro[MM_COROUTINE_NAME_FIELD_NAME] try: coro_allocated = int(coro[MM_COROUTINE_ALLOCATED_BYTES_FIELD_NAME]) coro_freed = int(coro[MM_COROUTINE_FREED_BYTES_FIELD_NAME]) coro_mem_used = coro_allocated - coro_freed memstat = f'{format_bytes(coro_allocated)}/{format_bytes(coro_freed)}/{format_bytes(coro_mem_used)}' except (gdb.error, KeyError): memstat = '?/?/?' current_coro_pref = ' ' if coro_id != current_coroutine_id else '*' gdb.write( f'{current_coro_pref}{coro_id}\t{coro_state}\t{coro_errno}\t{coro_func}\t{coro_arg}\t{coro_name}\t{memstat}\n') def _list_coroutines_for_thread(self, thread): thread.switch() gdb.write( f"Thread {thread.num} ({thread.name}) client coroutines:\n") mm_self_ptr = get_mm_self_or_none() if mm_self_ptr is None: gdb.write( f" There is no {MM_SELF_VARIABLE_NAME} in the current context. Does the executable actually use the machinarium framework?\n") return if mm_self_ptr == 0: gdb.write( f" The {MM_SELF_VARIABLE_NAME} is NULL, so no coroutines in this thread available.\n") return coroutines = mm_current_thread_coroutines('od_frontend') current_coroutine_id = mm_get_current_thread_coroutine_id() self._print_coroutines_list(coroutines, current_coroutine_id) def invoke(self, args, is_tty): with gdb_thread_restore(): for thread in self._get_threads_list_from_args(args): self._list_coroutines_for_thread(thread) gdb.write("\n") class ODListCurrentServers(gdb.Command): """List all servers accessible from any clients. Usage: info servers - list servers from client coroutines for all threads info servers - list servers from client coroutines for specified thread(s). can be thread id or name. Examples: (gdb) info servers (gdb) info servers thread-name1 42 (gdb) info servers 41 42 43 (gdb) info servers thread-name1 thread-name2 """ def __init__(self): super(ODListCurrentServers, self).__init__( "info servers", gdb.COMMAND_STACK, gdb.COMPLETE_EXPRESSION) def _get_threads_list_from_args(self, args): ids = set(gdb.string_to_argv(args)) if len(ids) == 0: return gdb.selected_inferior().threads() threads_list = [] for thr in gdb.selected_inferior().threads(): if thr.name in ids or str(thr.num) in ids: threads_list.append(thr) return threads_list def _print_servers_list(self, coroutines): gdb.write("cl-id\tsrv-id\tptr\tstate\n") for coro in coroutines: client_struct_ptr = coro[MM_COROUTINE_FUNCTION_ARG_NAME] client_struct_ptr = client_struct_ptr.cast(GDB_OD_CLIENT_PTR_TYPE) server_struct_ptr = client_struct_ptr.dereference( )[OD_CLIENT_SERVER_FIELD_NAME].cast(GDB_OD_SERVER_PTR_TYPE) client_id = client_struct_ptr.dereference()[ OD_SERVER_ID_FIELD_NAME]['id'] if server_struct_ptr == 0: gdb.write(f'{client_id}\tUNDEF\tUNDEF\tUNDEF\n') continue server = server_struct_ptr.dereference() server_id = server[OD_SERVER_ID_FIELD_NAME]['id'] server_state = server[OD_SERVER_STATE_FIELD_NAME] gdb.write(f'{client_id}\t{server_id}\t{server_struct_ptr}\t{server_state}\n') def _list_coroutines_for_thread(self, thread): thread.switch() gdb.write( f"Thread {thread.num} ({thread.name}) client coroutines:\n") mm_self_ptr = get_mm_self_or_none() if mm_self_ptr is None: gdb.write( f" There is no {MM_SELF_VARIABLE_NAME} in the current context. Does the executable actually use the machinarium framework?\n") return if mm_self_ptr == 0: gdb.write( f" The {MM_SELF_VARIABLE_NAME} is NULL, so no coroutines in this thread available.\n") return coroutines = mm_current_thread_coroutines('od_frontend') self._print_servers_list(coroutines) def invoke(self, args, is_tty): with gdb_thread_restore(): for thread in self._get_threads_list_from_args(args): self._list_coroutines_for_thread(thread) gdb.write("\n") class ODHashmapPrint(gdb.Command): """Print content of an Odyssey mm_hashmap_t. Usage: od-hashmap-print [max-entries] The command auto-detects the key/value layout based on the hashmap configuration (keysz/valsz): - Client/server/portals maps: key = char * (prepared statement / portal name) value = od_pstmt_t * (pointer) - Global prepared statements map: key = od_pstmt_desc_t { void *data; size_t len; } value = od_pstmt_t (stored inline) Examples: (gdb) od-hashmap-print client->prep_stmt_ids (gdb) od-hashmap-print client->portals (gdb) od-hashmap-print server->prep_stmts (gdb) od-hashmap-print gm->hm (gdb) od-hashmap-print gm->hm 1000 (gdb) od-hashmap-print 0x60351b012345 """ def __init__(self): super(ODHashmapPrint, self).__init__( "od-hashmap-print", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) def _read_ptr_at(self, addr): addr = int(addr) return int(gdb.parse_and_eval(f'(void **){hex(addr)}').dereference()) def _read_size_t_at(self, addr): addr = int(addr) return int( gdb.parse_and_eval( f'({GDB_SIZE_T_TYPE.tag}*){hex(addr)}' ).dereference() ) def _read_uint64_at(self, addr): addr = int(addr) return int( gdb.parse_and_eval( f'({GDB_UINT64_TYPE.tag}*){hex(addr)}' ).dereference() ) def _read_cstring(self, addr): try: return gdb.parse_and_eval( f'(char *){hex(int(addr))}' ).string() except gdb.error: return None def _detect_kind(self, hm_val): """Detect hashmap kind based on keysz/valsz. Returns one of: 'global', 'ptr_key_ptr_val'. - global: key=od_pstmt_desc_t (16B), value=od_pstmt_t (inline) - ptr_key_ptr_val: key=char*, value=od_pstmt_t* (both 8B) """ keysz = int(hm_val['keysz']) valsz = int(hm_val['valsz']) ptr_sz = int(gdb.parse_and_eval('sizeof(void *)')) if keysz == int(GDB_OD_PSTMT_DESC_TYPE.sizeof) and \ valsz == int(GDB_OD_PSTMT_TYPE.sizeof): return 'global' if keysz == ptr_sz and valsz == ptr_sz: return 'ptr_key_ptr_val' raise gdb.error( f"unsupported hashmap layout: keysz={keysz} valsz={valsz} " f"(ptr_sz={ptr_sz})" ) def _kvp_iterate(self, hm_val): """Yield (bucket_index, kvp_addr, hash) for each kvp in the hashmap. mm_hashmap_kvp_t layout: { mm_list_t link; mm_hash_t hash; uint8_t keyval[]; } link is the first field, so kvp == &link. """ link_offset = mm_get_field_offset( GDB_MM_HASHMAP_KVP_TYPE, MM_HASHMAP_KVP_LINK_FIELD_NAME) hash_offset = mm_get_field_offset( GDB_MM_HASHMAP_KVP_TYPE, MM_HASHMAP_KVP_HASH_FIELD_NAME) nbuckets = int(hm_val['nbuckets']) buckets = hm_val['buckets'] for bi in range(nbuckets): bucket = (buckets + bi).dereference() head = bucket['kvps'].address # mm_list_t sentinel head_addr = int(head) cur = int(head['next']) while cur != 0 and cur != head_addr: kvp_addr = cur - link_offset hash_val = int( gdb.parse_and_eval( f'({GDB_UINT64_TYPE.tag}*){hex(kvp_addr + hash_offset)}' ).dereference() ) yield bi, kvp_addr, hash_val cur = int( gdb.parse_and_eval( f'(mm_list_t *){hex(cur)}' ).dereference()['next'] ) def _print_global_entry(self, idx, bi, hash_val, kvp_addr, keyoff, valoff): # key: od_pstmt_desc_t { void *data; size_t len; } key_addr = kvp_addr + keyoff desc_data = self._read_ptr_at(key_addr) desc_len = self._read_size_t_at(key_addr + int( gdb.parse_and_eval('sizeof(void *)'))) # value: od_pstmt_t inline val_addr = kvp_addr + valoff pstmt_val = gdb.parse_and_eval( f'({OD_PSTMT_TYPE_NAME} *){hex(val_addr)}' ).dereference() pstmt_name = str(pstmt_val[OD_PSTMT_NAME_FIELD_NAME].string()) try: refs = int(pstmt_val[OD_PSTMT_REFS_FIELD_NAME]) except (gdb.error, KeyError): refs = -1 pstmt_desc = pstmt_val[OD_PSTMT_DESC_FIELD_NAME] pstmt_desc_data = int(pstmt_desc[OD_PSTMT_DESC_DATA_FIELD_NAME]) pstmt_desc_len = int(pstmt_desc[OD_PSTMT_DESC_LEN_FIELD_NAME]) # try to render key data as SQL string sql = None if desc_data != 0 and desc_len > 0: try: sql = gdb.parse_and_eval( f'(char *){hex(desc_data)}' ).string() except gdb.error: sql = None gdb.write( f"[{idx}] bucket={bi} hash={hash_val:#x} " f"key={{data={hex(desc_data)}, len={desc_len}}} " f"val={{name=\"{pstmt_name}\", " f"desc.data={hex(pstmt_desc_data)}, " f"desc.len={pstmt_desc_len}, refs={refs}}}" ) if sql is not None: gdb.write(f" sql=\"{sql}\"") gdb.write("\n") def _print_ptr_entry(self, idx, bi, hash_val, kvp_addr, keyoff, valoff): # key: char * (C string) key_addr = kvp_addr + keyoff key_ptr = self._read_ptr_at(key_addr) key_str = self._read_cstring(key_ptr) or "" # value: od_pstmt_t * val_addr = kvp_addr + valoff pstmt_ptr = self._read_ptr_at(val_addr) if pstmt_ptr != 0: pstmt_val = gdb.parse_and_eval( f'({OD_PSTMT_TYPE_NAME} *){hex(pstmt_ptr)}' ).dereference() try: pstmt_name = str(pstmt_val[OD_PSTMT_NAME_FIELD_NAME].string()) except gdb.error: pstmt_name = "" try: refs = int(pstmt_val[OD_PSTMT_REFS_FIELD_NAME]) except (gdb.error, KeyError): refs = -1 gdb.write( f"[{idx}] bucket={bi} hash={hash_val:#x} " f"key=\"{key_str}\" " f"val->{{name=\"{pstmt_name}\", " f"refs={refs}, @ {hex(pstmt_ptr)}}}\n" ) else: gdb.write( f"[{idx}] bucket={bi} hash={hash_val:#x} " f"key=\"{key_str}\" val=NULL\n" ) def invoke(self, args, _): argv = gdb.string_to_argv(args) if len(argv) < 1: gdb.write( 'Expected 1-2 arguments: mm_hashmap_t* expression ' 'and optional max-entries limit\n', stream=gdb.STDLOG) return expr = argv[0] limit = int(argv[1]) if len(argv) > 1 else 10 ** 9 ptr = gdb.parse_and_eval(expr) if ptr.type.code != gdb.TYPE_CODE_PTR: # bare address / non-pointer expression — cast to mm_hashmap_t* ptr = ptr.cast(GDB_MM_HASHMAP_POINTER_TYPE) if int(ptr) == 0: gdb.write("hashmap pointer is NULL\n", stream=gdb.STDLOG) return hm_val = ptr.dereference() kind = self._detect_kind(hm_val) keyoff = int(hm_val['keyoff']) valoff = int(hm_val['valoff']) kvpsize = int(hm_val['kvpsize']) nbuckets = int(hm_val['nbuckets']) hm_addr = int(hm_val.address) if hm_val.address is not None else 0 gdb.write( f"mm_hashmap_t @ {hex(hm_addr)}: " f"kind={kind} nbuckets={nbuckets} kvpsize={kvpsize} " f"keyoff={keyoff} valoff={valoff}\n" ) count = 0 for bi, kvp_addr, hash_val in self._kvp_iterate(hm_val): if kind == 'global': self._print_global_entry( count, bi, hash_val, kvp_addr, keyoff, valoff) else: self._print_ptr_entry( count, bi, hash_val, kvp_addr, keyoff, valoff) count += 1 if count >= limit: gdb.write(f"... stopped after {limit} entries\n") return gdb.write(f"total entries: {count}\n") MMCoroutines() MMCoroutineCmd() IgnoreErrorsCmd() ODListPrint() ODListPrintSelect() ODGetFieldOffset() ODClientCoroutines() ODListCurrentServers() ODHashmapPrint() gdb.write('done.\n', stream=gdb.STDLOG) odyssey-1.6.0-rc1/sources/machinarium/io.c000066400000000000000000000324121526064202200204340ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include static void mm_io_on_read_cb(mm_fd_t *handle) { mm_io_t *io = handle->on_read_arg; io->read_ready = 1; mm_cond_signal(&io->cond); } static void mm_io_on_write_cb(mm_fd_t *handle) { mm_io_t *io = handle->on_write_arg; mm_cond_signal(&io->cond); } static void mm_io_on_err_cb(mm_fd_t *handle) { mm_io_t *io = handle->on_write_arg; mm_cond_signal(&io->cond); io->errored = 1; io->error = mm_socket_error(handle->fd); io->connected = 0; } static void mm_io_on_close_cb(mm_fd_t *handle) { mm_io_t *io = handle->on_write_arg; mm_cond_signal(&io->cond); io->connected = 0; } MACHINE_API machine_tls_t *machine_tls_create(void) { mm_errno_set(0); mm_tls_t *tls; tls = mm_malloc(sizeof(*tls)); if (tls == NULL) { mm_errno_set(ENOMEM); return NULL; } tls->verify = MM_TLS_NONE; tls->server = NULL; tls->protocols = NULL; tls->ca_path = NULL; tls->ca_file = NULL; tls->cert_file = NULL; tls->key_file = NULL; return (machine_tls_t *)tls; } MACHINE_API void machine_tls_free(machine_tls_t *obj) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); if (tls->protocols) { mm_free(tls->protocols); } if (tls->server) { mm_free(tls->server); } if (tls->ca_path) { mm_free(tls->ca_path); } if (tls->ca_file) { mm_free(tls->ca_file); } if (tls->cert_file) { mm_free(tls->cert_file); } if (tls->key_file) { mm_free(tls->key_file); } mm_free(tls); } MACHINE_API int machine_tls_set_verify(machine_tls_t *obj, char *mode) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); if (strcasecmp(mode, "none") == 0) { tls->verify = MM_TLS_NONE; } else if (strcasecmp(mode, "peer") == 0) { tls->verify = MM_TLS_PEER; } else if (strcasecmp(mode, "peer_strict") == 0) { tls->verify = MM_TLS_PEER_STRICT; } else { return -1; } return 0; } MACHINE_API int machine_tls_set_server(machine_tls_t *obj, char *name) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); char *string = mm_strdup(name); if (string == NULL) { mm_errno_set(ENOMEM); return -1; } if (tls->server) { mm_free(tls->server); } tls->server = string; return 0; } MACHINE_API int machine_tls_set_protocols(machine_tls_t *obj, char *protocols) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); char *string = mm_strdup(protocols); if (string == NULL) { mm_errno_set(ENOMEM); return -1; } if (tls->protocols) { mm_free(tls->protocols); } tls->protocols = string; return 0; } MACHINE_API int machine_tls_set_ca_path(machine_tls_t *obj, char *path) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); char *string = mm_strdup(path); if (string == NULL) { mm_errno_set(ENOMEM); return -1; } if (tls->ca_path) { mm_free(tls->ca_path); } tls->ca_path = string; return 0; } MACHINE_API int machine_tls_set_ca_file(machine_tls_t *obj, char *path) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); char *string = mm_strdup(path); if (string == NULL) { mm_errno_set(ENOMEM); return -1; } if (tls->ca_file) { mm_free(tls->ca_file); } tls->ca_file = string; return 0; } MACHINE_API int machine_tls_set_cert_file(machine_tls_t *obj, char *path) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); char *string = mm_strdup(path); if (string == NULL) { mm_errno_set(ENOMEM); return -1; } if (tls->cert_file) { mm_free(tls->cert_file); } tls->cert_file = string; return 0; } MACHINE_API int machine_tls_set_key_file(machine_tls_t *obj, char *path) { mm_tls_t *tls = mm_cast(mm_tls_t *, obj); mm_errno_set(0); char *string = mm_strdup(path); if (string == NULL) { mm_errno_set(ENOMEM); return -1; } if (tls->key_file) { mm_free(tls->key_file); } tls->key_file = string; return 0; } int mm_io_set_tls(mm_io_t *io, machine_tls_t *tls, uint32_t timeout) { if (io->tls) { mm_errno_set(EINPROGRESS); return -1; } io->tls = mm_cast(mm_tls_t *, tls); return mm_tls_handshake(io, timeout); } int mm_io_is_tls(mm_io_t *io) { return io->tls != NULL; } int mm_io_set_compression(mm_io_t *io, char algorithm) { if (io->zpq_stream) { mm_errno_set(EINPROGRESS); return -1; } int impl = mm_zpq_get_algorithm_impl(algorithm); if (impl >= 0) { io->zpq_stream = zpq_create(impl, (mm_zpq_tx_func)mm_io_write, (mm_zpq_rx_func)mm_io_read, io, NULL, 0); return 0; } return -1; } mm_io_t *mm_io_create(void) { mm_errno_set(0); mm_io_t *io = mm_malloc(sizeof(*io)); if (io == NULL) { mm_errno_set(ENOMEM); return NULL; } memset(io, 0, sizeof(*io)); io->fd = -1; mm_cond_init(&io->cond); mm_tls_init(io); return io; } MACHINE_API void mm_io_free(mm_io_t *obj) { mm_io_t *io = mm_cast(mm_io_t *, obj); mm_errno_set(0); mm_tls_free(io); mm_compression_free(io); mm_free(io); } char *mm_io_error(mm_io_t *io) { if (io->tls_error) { return io->tls_error_msg; } if (io->error) { return strerror(io->error); } int errno_ = mm_errno_get(); if (errno_) { return strerror(errno_); } errno_ = errno; if (errno_) { return strerror(errno_); } return NULL; } int mm_io_fd(mm_io_t *io) { return io->fd; } int mm_io_set_nodelay(mm_io_t *io, int enable) { mm_errno_set(0); io->opt_nodelay = enable; if (io->fd != -1) { int rc; rc = mm_socket_set_nodelay(io->fd, enable); if (rc == -1) { mm_errno_set(errno); return -1; } } return 0; } int mm_io_set_nolinger(mm_io_t *io) { mm_errno_set(0); int rc; rc = mm_socket_set_nolinger(io->fd); if (rc == -1) { mm_errno_set(errno); return -1; } return 0; } int mm_io_set_keepalive(mm_io_t *io, int enable, int delay, int interval, int probes, int usr_timeout) { mm_errno_set(0); io->opt_keepalive = enable; io->opt_keepalive_delay = delay; io->opt_keepalive_interval = interval; io->opt_keepalive_probes = probes; io->opt_keepalive_usr_timeout = usr_timeout; if (io->fd != -1) { int rc; rc = mm_socket_set_keepalive(io->fd, enable, delay, interval, probes, usr_timeout); if (rc == -1) { mm_errno_set(errno); return -1; } } return 0; } int mm_io_advice_keepalive_usr_timeout(int delay, int interval, int probes) { return mm_socket_advice_keepalive_usr_timeout(delay, interval, probes); } int mm_io_attach(mm_io_t *io) { mm_errno_set(0); if (io->attached) { mm_errno_set(EINPROGRESS); return -1; } int rc; rc = mm_loop_add(&mm_self->loop, &io->handle, mm_io_on_read_cb, io, mm_io_on_write_cb, io, mm_io_on_err_cb, io, mm_io_on_close_cb, io); if (rc == -1) { mm_errno_set(errno); return -1; } io->attached = 1; return 0; } int mm_io_detach(mm_io_t *io) { mm_errno_set(0); if (!io->attached) { mm_errno_set(ENOTCONN); return -1; } int rc; rc = mm_loop_delete(&mm_self->loop, &io->handle); if (rc == -1) { mm_errno_set(errno); return -1; } io->attached = 0; return 0; } int mm_io_verify(mm_io_t *io, char *common_name) { mm_errno_set(0); if (io->tls == NULL) { mm_errno_set(EINVAL); return -1; } int rc; rc = mm_tls_verify_common_name(io, common_name); return rc; } int mm_io_socket_set(mm_io_t *io, int fd) { io->fd = fd; int rc; rc = mm_socket_set_nosigpipe(io->fd, 1); if (rc == -1) { mm_errno_set(errno); return -1; } if (!io->is_unix_socket) { if (io->opt_nodelay) { rc = mm_socket_set_nodelay(io->fd, 1); if (rc == -1) { mm_errno_set(errno); return -1; } } if (io->opt_keepalive) { rc = mm_socket_set_keepalive( io->fd, 1, io->opt_keepalive_delay, io->opt_keepalive_interval, io->opt_keepalive_probes, io->opt_keepalive_usr_timeout); if (rc == -1) { mm_errno_set(errno); return -1; } } } io->handle.fd = io->fd; return 0; } int mm_io_socket(mm_io_t *io, struct sockaddr *sa) { if (sa->sa_family == AF_UNIX) { io->is_unix_socket = 1; } int fd; #if defined(SOCK_NONBLOCK) fd = mm_socket(sa->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); #else fd = mm_socket(sa->sa_family, SOCK_STREAM, 0); if (fd != -1) { if (mm_socket_set_nonblock(fd, 1) == -1) { int err = errno; close(fd); errno = err; fd = -1; } } #endif if (fd == -1) { mm_errno_set(errno); return -1; } return mm_io_socket_set(io, fd); } ssize_t mm_io_write(mm_io_t *io, const void *buf, size_t size) { mm_errno_set(0); ssize_t rc; if (mm_tls_is_active(io)) { rc = mm_tls_write(io, buf, size); } else { rc = mm_socket_write(io->fd, buf, size); } if (rc > 0) { return rc; } int errno_ = errno; mm_errno_set(errno_); if (machine_errno_retryable(errno_)) { return -1; } io->connected = 0; return -1; } ssize_t mm_io_read(mm_io_t *io, void *buf, size_t size) { mm_errno_set(0); ssize_t rc; if (mm_tls_is_active(io)) { rc = mm_tls_read(io, buf, size); } else { rc = mm_socket_read(io->fd, buf, size); } if (rc > 0) { /* do not set read_ready to 0 - there can be more bytes in socket */ return rc; } io->read_ready = 0; if (rc < 0) { int errno_ = errno; mm_errno_set(errno_); if (machine_errno_retryable(errno_)) { return -1; } } /* error or eof */ io->connected = 0; return rc; } int mm_io_read_pending(mm_io_t *io) { if (mm_tls_is_active(io)) { if (mm_tls_read_pending(io)) { return 1; } } return mm_socket_read_pending(io->fd); } static inline int format_inet_socket_addr(struct sockaddr *sa, socklen_t sa_len, char *buf, size_t buflen) { static MM_THREAD_LOCAL char host[NI_MAXHOST]; static MM_THREAD_LOCAL char serv[NI_MAXSERV]; int rc = getnameinfo(sa, sa_len, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); if (rc != 0) { snprintf(buf, buflen, "getnameinfo error: %s", gai_strerror(rc)); return -1; } if (sa->sa_family == AF_INET6) { snprintf(buf, buflen, "[%s]:%s", host, serv); } else { snprintf(buf, buflen, "%s:%s", host, serv); } return 0; } static inline int format_unix_socket_addr(char *buf, size_t buflen) { /* TODO: implement unix sock name formatting */ snprintf(buf, buflen, "unix"); return 0; } int mm_io_format_socket_addr(mm_io_t *io, char *buf, size_t buflen) { struct sockaddr_storage sa; socklen_t sa_len = sizeof(sa); if (getsockname(io->handle.fd, (struct sockaddr *)&sa, &sa_len) < 0) { return -1; } if (sa.ss_family == AF_INET || sa.ss_family == AF_INET6) { return format_inet_socket_addr((struct sockaddr *)&sa, sa_len, buf, buflen); } else if (sa.ss_family == AF_UNIX) { return format_unix_socket_addr(buf, buflen); } snprintf(buf, buflen, "(unknown family %d)", sa.ss_family); return -1; } int mm_io_wait(mm_io_t *io, uint32_t timeout_ms) { if (mm_unlikely(!io->attached)) { abort(); } return mm_cond_wait(&io->cond, timeout_ms); } void mm_io_set_peer(mm_io_t *io, mm_io_t *peer) { if (mm_unlikely(peer->cond.propagate != NULL)) { abort(); } mm_cond_propagate(&peer->cond, &io->cond); } void mm_io_remove_peer(mm_io_t *io, mm_io_t *peer) { if (mm_unlikely(peer->cond.propagate != &io->cond)) { abort(); } mm_cond_propagate(&peer->cond, NULL); } int mm_tls_is_active(mm_io_t *io) { return io->tls_ssl != NULL; } int mm_io_last_event(mm_io_t *io) { return io->handle.last_event; } void mm_io_set_deadline(mm_io_t *io, uint32_t timeout_ms) { uint64_t now_ms = machine_time_ms(); uint64_t deadline_ms; if (timeout_ms != UINT32_MAX) { if (now_ms > UINT64_MAX - timeout_ms) { deadline_ms = UINT64_MAX; } else { deadline_ms = now_ms + timeout_ms; } } else { deadline_ms = UINT64_MAX; } io->deadline_ms = deadline_ms; } static uint32_t mm_io_deadline_left_ms(mm_io_t *io) { assert(io->deadline_ms > 0); uint64_t now_ms = machine_time_ms(); if (now_ms >= io->deadline_ms) { return 0; } if (io->deadline_ms != UINT64_MAX) { if (io->deadline_ms - now_ms >= UINT32_MAX) { return UINT32_MAX; } return (uint32_t)(io->deadline_ms - now_ms); } return UINT32_MAX; } int mm_io_wait_deadline(mm_io_t *io) { uint32_t left_ms = mm_io_deadline_left_ms(io); if (left_ms == 0) { mm_errno_set(ETIMEDOUT); return MM_COND_WAIT_FAIL; } return mm_io_wait(io, left_ms); } int mm_io_poll(mm_io_t *io) { /* no need to check IN/OUT - we call read/write before awaiting anyway */ if (!io->attached) { mm_errno_set(ENOTCONN); return -1; } mm_fd_t *fd = &io->handle; struct pollfd pfd; memset(&pfd, 0, sizeof(struct pollfd)); pfd.fd = fd->fd; #if defined(POLLRDHUP) pfd.events = POLLERR | POLLHUP | POLLRDHUP; #else pfd.events = POLLERR | POLLHUP; #endif int rc = poll(&pfd, 1, 0); if (rc < 0) { mm_errno_set(errno); return rc; } if (rc == 0) { /* no events, ok */ return 0; } assert(rc == 1); int events = pfd.events; int err = events & POLLERR; #if defined(POLLRDHUP) int close = events & POLLHUP || events & POLLRDHUP; #else int close = events & POLLHUP; #endif if (err && fd->mask & MM_ERR) { fd->on_err(&io->handle); } if (close && fd->mask & MM_CLOSE) { fd->on_close(&io->handle); } return 0; } int mm_io_shutdown(mm_io_t *io) { mm_errno_set(0); int rc = shutdown(io->fd, SHUT_RDWR); if (rc == -1) { return MM_NOTOK_RETCODE; } return MM_OK_RETCODE; } int mm_io_want_read(mm_io_t *io) { return io->want & MM_IO_WANT_READ; } int mm_io_want_write(mm_io_t *io) { return io->want & MM_IO_WANT_WRITE; } int mm_io_read_ready(mm_io_t *io) { return io->read_ready; } odyssey-1.6.0-rc1/sources/machinarium/kqueue.c000066400000000000000000000121421526064202200213220ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include #include typedef struct mm_kqueue_t mm_kqueue_t; struct mm_kqueue_t { mm_poll_t poll; int fd; struct kevent *list; int size; int count; }; static mm_poll_t *mm_kqueue_create(void) { mm_kqueue_t *kq; kq = mm_malloc(sizeof(mm_kqueue_t)); if (kq == NULL) { return NULL; } kq->poll.iface = &mm_kqueue_if; kq->count = 0; kq->size = 1024; int size = sizeof(struct kevent) * kq->size; kq->list = mm_malloc(size); if (kq->list == NULL) { mm_free(kq); return NULL; } memset(kq->list, 0, size); kq->fd = kqueue(); if (kq->fd == -1) { mm_free(kq->list); mm_free(kq); return NULL; } fcntl(kq->fd, F_SETFD, FD_CLOEXEC); return &kq->poll; } static void mm_kqueue_free(mm_poll_t *poll) { mm_kqueue_t *kq = (mm_kqueue_t *)poll; if (kq->list) { mm_free(kq->list); } mm_free(poll); } static int mm_kqueue_shutdown(mm_poll_t *poll) { mm_kqueue_t *kq = (mm_kqueue_t *)poll; if (kq->fd != -1) { close(kq->fd); kq->fd = -1; } return 0; } static int mm_kqueue_step(mm_poll_t *poll, int timeout) { mm_kqueue_t *kq = (mm_kqueue_t *)poll; if (kq->count == 0) { return 0; } struct timespec ts; struct timespec *tsp = NULL; if (timeout >= 0) { ts.tv_sec = timeout / 1000; ts.tv_nsec = (timeout % 1000) * 1000000L; tsp = &ts; } int count; count = kevent(kq->fd, NULL, 0, kq->list, kq->size, tsp); if (count <= 0) { return 0; } int i = 0; while (i < count) { struct kevent *ev = &kq->list[i]; mm_fd_t *fd = ev->udata; fd->last_event = 0; int is_eof = (ev->flags & EV_EOF) != 0; /* real error condition: EOF + non-zero fflags (errno) */ int is_err = is_eof && ev->fflags != 0; if (is_err && (fd->mask & MM_ERR)) { fd->last_event |= MM_ERR; assert(fd->on_err); fd->on_err(fd); } if (is_eof && (fd->mask & MM_CLOSE)) { fd->last_event |= MM_CLOSE; assert(fd->on_close); fd->on_close(fd); } if (ev->filter == EVFILT_READ && (fd->mask & MM_R)) { fd->last_event |= MM_R; assert(fd->on_read); fd->on_read(fd); } if (ev->filter == EVFILT_WRITE && (fd->mask & MM_W)) { fd->last_event |= MM_W; assert(fd->on_write); fd->on_write(fd); } i++; } return count; } static int mm_kqueue_resize(mm_kqueue_t *kq) { if (kq->count + 1 <= kq->size) { return 0; } int size = kq->size * 2; void *ptr = mm_realloc(kq->list, sizeof(struct kevent) * size); if (ptr == NULL) { return -1; } kq->list = ptr; kq->size = size; return 0; } static inline int mm_kqueue_change(mm_kqueue_t *kq, int fd, int16_t filter, u_short flags, void *udata) { struct kevent ev; EV_SET(&ev, fd, filter, flags, 0, 0, udata); return kevent(kq->fd, &ev, 1, NULL, 0, NULL); } static int mm_kqueue_modify(mm_poll_t *poll, mm_fd_t *fd, int mask) { if (fd->mask == mask) { return 0; } mm_kqueue_t *kq = (mm_kqueue_t *)poll; int rc; int old_r = fd->mask & MM_R; int new_r = mask & MM_R; int old_w = fd->mask & MM_W; int new_w = mask & MM_W; if (new_r && !old_r) { if (mm_kqueue_resize(kq) == -1) { return -1; } rc = mm_kqueue_change(kq, fd->fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, fd); if (rc == -1) { return -1; } kq->count++; } else if (!new_r && old_r) { rc = mm_kqueue_change(kq, fd->fd, EVFILT_READ, EV_DELETE, fd); if (rc == -1) { return -1; } kq->count--; } if (new_w && !old_w) { if (mm_kqueue_resize(kq) == -1) { return -1; } rc = mm_kqueue_change(kq, fd->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_CLEAR, fd); if (rc == -1) { return -1; } kq->count++; } else if (!new_w && old_w) { rc = mm_kqueue_change(kq, fd->fd, EVFILT_WRITE, EV_DELETE, fd); if (rc == -1) { return -1; } kq->count--; } fd->mask = mask; return 0; } static int mm_kqueue_add(mm_poll_t *poll, mm_fd_t *fd, mm_fd_callback_t on_read, void *on_read_arg, mm_fd_callback_t on_write, void *on_write_arg, mm_fd_callback_t on_err, void *on_err_arg, mm_fd_callback_t on_close, void *on_close_arg) { fd->on_read = on_read; fd->on_read_arg = on_read_arg; fd->on_write = on_write; fd->on_write_arg = on_write_arg; fd->on_err = on_err; fd->on_err_arg = on_err_arg; fd->on_close = on_close; fd->on_close_arg = on_close_arg; int mask = 0; if (fd->on_read != NULL) { mask |= MM_R; } if (fd->on_write != NULL) { mask |= MM_W; } if (fd->on_err != NULL) { mask |= MM_ERR; } if (fd->on_close != NULL) { mask |= MM_CLOSE; } return mm_kqueue_modify(poll, fd, mask); } static int mm_kqueue_del(mm_poll_t *poll, mm_fd_t *fd) { fd->on_read = NULL; fd->on_read_arg = NULL; fd->on_write = NULL; fd->on_write_arg = NULL; return mm_kqueue_modify(poll, fd, 0); } mm_pollif_t mm_kqueue_if = { .name = "kqueue", .create = mm_kqueue_create, .free = mm_kqueue_free, .shutdown = mm_kqueue_shutdown, .step = mm_kqueue_step, .add = mm_kqueue_add, .del = mm_kqueue_del }; odyssey-1.6.0-rc1/sources/machinarium/loop.c000066400000000000000000000033241526064202200207760ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #if defined(__linux__) #include #define MM_POLL_IF mm_epoll_if #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ defined(__OpenBSD__) || defined(__DragonFly__) #include #define MM_POLL_IF mm_kqueue_if #else #error "machinarium: unsupported platform, no poll backend available" #endif int mm_loop_init(mm_loop_t *loop) { loop->poll = MM_POLL_IF.create(); if (loop->poll == NULL) { return -1; } mm_clock_init(&loop->clock); mm_clock_update(&loop->clock); memset(&loop->idle, 0, sizeof(loop->idle)); return 0; } int mm_loop_shutdown(mm_loop_t *loop) { mm_clock_free(&loop->clock); int rc; rc = loop->poll->iface->shutdown(loop->poll); if (rc == -1) { return -1; } loop->poll->iface->free(loop->poll); return 0; } int mm_loop_step(mm_loop_t *loop) { /* update clock time */ mm_clock_reset(&loop->clock); if (loop->clock.active) { mm_clock_update(&loop->clock); } /* run idle callback */ int rc; if (loop->idle.callback) { rc = loop->idle.callback(&loop->idle); if (!rc) { return 0; } } /* get minimal timer timeout in case of no timers - just poll for 1 sec, this will not create cpu load */ int timeout_ms = 1000; mm_timer_t *next; next = mm_clock_timer_min(&loop->clock); if (next) { int64_t diff = next->timeout - loop->clock.time_ms; if (diff <= 0) { timeout_ms = 0; } else { timeout_ms = diff; } } /* run timers */ mm_clock_step(&loop->clock); /* poll for events */ rc = loop->poll->iface->step(loop->poll, timeout_ms); if (rc == -1) { return -1; } return 0; } odyssey-1.6.0-rc1/sources/machinarium/lrand48.c000066400000000000000000000053101526064202200212760ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include /* raw copy postgres rand48 impl for better compatibility */ #define RAND48_SEED_0 (0x330e) #define RAND48_SEED_1 (0xabcd) #define RAND48_SEED_2 (0x1234) #define RAND48_MULT_0 (0xe66d) #define RAND48_MULT_1 (0xdeec) #define RAND48_MULT_2 (0x0005) #define RAND48_ADD (0x000b) static unsigned short _rand48_mult[3] = { RAND48_MULT_0, RAND48_MULT_1, RAND48_MULT_2 }; static unsigned short _rand48_add = RAND48_ADD; static inline void _dorand48(unsigned short xseed[3]) { unsigned long accu; unsigned short temp[2]; accu = (unsigned long)_rand48_mult[0] * (unsigned long)xseed[0] + (unsigned long)_rand48_add; temp[0] = (unsigned short)accu; /* lower 16 bits */ accu >>= sizeof(unsigned short) * 8; accu += (unsigned long)_rand48_mult[0] * (unsigned long)xseed[1] + (unsigned long)_rand48_mult[1] * (unsigned long)xseed[0]; temp[1] = (unsigned short)accu; /* middle 16 bits */ accu >>= sizeof(unsigned short) * 8; accu += (long)_rand48_mult[0] * xseed[2] + (long)_rand48_mult[1] * xseed[1] + (long)_rand48_mult[2] * xseed[0]; xseed[0] = temp[0]; xseed[1] = temp[1]; xseed[2] = (unsigned short)accu; } static inline double pg_impl_erand48(unsigned short xseed[3]) { _dorand48(xseed); return ldexp((double)xseed[0], -48) + ldexp((double)xseed[1], -32) + ldexp((double)xseed[2], -16); } static inline long int pg_impl_lrand48(unsigned short *_rand48_seed) { _dorand48(_rand48_seed); return ((long)_rand48_seed[2] << 15) + ((long)_rand48_seed[1] >> 1); } static inline void pg_impl_srand48(long seed, unsigned short *_rand48_seed) { _rand48_seed[0] = RAND48_SEED_0; _rand48_seed[1] = (unsigned short)seed; _rand48_seed[2] = (unsigned short)(seed >> 16); } MM_THREAD_LOCAL unsigned short prng_seed[3]; MM_THREAD_LOCAL unsigned short *prng_state = NULL; void mm_lrand48_seed(void) { struct timeval tv; gettimeofday(&tv, NULL); long int rand_seed_2 = 0; long int rand_seed; rand_seed = getpid() ^ getuid() ^ tv.tv_sec ^ tv.tv_usec; int fd; fd = open("/dev/urandom", O_RDONLY); if (fd == -1) { fd = open("/dev/random", O_RDONLY); } if (fd != -1) { int rc = read(fd, &rand_seed_2, sizeof(rand_seed_2)); (void)rc; close(fd); } rand_seed ^= rand_seed_2; pg_impl_srand48(rand_seed, prng_seed); prng_state = prng_seed; } MACHINE_API void machine_lrand48_seed(void) { mm_lrand48_seed(); } MACHINE_API long int machine_lrand48(void) { assert(prng_state); return pg_impl_lrand48(prng_state); } MACHINE_API double machine_erand48(unsigned short xseed[3]) { return pg_impl_erand48(xseed); } odyssey-1.6.0-rc1/sources/machinarium/machine.c000066400000000000000000000270041526064202200214320ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_TSAN #include #endif __thread mm_machine_t *mm_self = NULL; static int mm_idle_cb(mm_idle_t *handle) { (void)handle; mm_scheduler_run(&mm_self->scheduler); return mm_scheduler_online(&mm_self->scheduler); } static inline void machine_instance_free(mm_machine_t *machine) { /* todo: check active timers and other allocated * resources */ mm_msgcache_free(&machine->msg_cache); mm_coroutine_cache_free(&machine->system_coroutine_cache); mm_coroutine_cache_free(&machine->general_coroutine_cache); mm_eventmgr_free(&machine->event_mgr, &machine->loop); mm_signalmgr_free(&machine->signal_mgr, &machine->loop); mm_loop_shutdown(&machine->loop); mm_scheduler_free(&machine->scheduler); } static inline void free_tls_container(struct mm_tls_ctx *ctx_container) { while (ctx_container != NULL) { struct mm_tls_ctx *next = ctx_container->next; mm_tls_ctx_free(ctx_container); ctx_container = next; } } static void *machine_main(void *arg) { mm_machine_t *machine = arg; mm_self = machine; mm_thread_disable_cancel(); /* set thread name */ if (machine->name) { mm_thread_set_name(machine->name); } #ifdef HAVE_TSAN char name[256]; if (machine->name) { snprintf(name, sizeof(name), "%s (in machine_main)", machine->name); } else { snprintf(name, sizeof(name), "Machine ID: %" PRIu64 " (in machine_main)", mm_self->id); } __tsan_set_fiber_name(__tsan_get_current_fiber(), name); #endif mm_lrand48_seed(); /* create main coroutine */ int64_t id; id = machine_coroutine_create_system(machine->main, machine->main_arg, machine->name); (void)id; /* run main loop */ atomic_store(&machine->online, 1); for (;;) { if (!(mm_scheduler_online(&machine->scheduler) && atomic_load(&machine->online))) { break; } mm_loop_step(&machine->loop); } if (machine->global_dtor != NULL) { machine->global_dtor(machine->thread_global_private); } free_tls_container(machine->client_tls_ctx); free_tls_container(machine->server_tls_ctx); for (size_t i = 0; i < machine->natexit; ++i) { mm_machine_atexit_entry_t *e = &machine->atexit[i]; e->fptr(e->arg); } mm_wait_flag_set(machine->join_flag); atomic_store(&machine->online, 0); machine_instance_free(machine); return NULL; } MACHINE_API int64_t machine_create(char *name, machine_coroutine_t function, void *arg) { mm_machine_t *machine; machine = mm_malloc(sizeof(*machine)); if (machine == NULL) { return -1; } atomic_init(&machine->online, 0); machine->id = 0; machine->main = function; machine->main_arg = arg; machine->server_tls_ctx = NULL; machine->client_tls_ctx = NULL; machine->name = NULL; machine->global_dtor = NULL; machine->natexit = 0; memset(machine->atexit, 0, sizeof(machine->atexit)); #ifdef MM_MEM_PROF machine->allocated_bytes = 0; machine->freed_bytes = 0; #endif machine->join_flag = mm_wait_flag_create(); if (machine->join_flag == NULL) { mm_free(machine); mm_errno_set(ENOMEM); return -1; } if (name) { machine->name = mm_strdup(name); if (machine->name == NULL) { mm_free(machine); return -1; } } mm_list_init(&machine->link); mm_msgcache_init(&machine->msg_cache); mm_msgcache_set_gc_watermark(&machine->msg_cache, machinarium.config.msg_cache_gc_size); mm_msgcache_set_gc_count_watermark( &machine->msg_cache, machinarium.config.msg_cache_gc_count); mm_coroutine_cache_init(&machine->system_coroutine_cache, machinarium.config.system_stack_size * machinarium.config.page_size, machinarium.config.page_size, machinarium.config.coroutine_cache_size); mm_coroutine_cache_init(&machine->general_coroutine_cache, machinarium.config.stack_size * machinarium.config.page_size, machinarium.config.page_size, machinarium.config.coroutine_cache_size); mm_scheduler_init(&machine->scheduler); int rc; rc = mm_loop_init(&machine->loop); if (rc < 0) { mm_scheduler_free(&machine->scheduler); mm_free(machine); return -1; } mm_loop_set_idle(&machine->loop, mm_idle_cb, NULL); rc = mm_eventmgr_init(&machine->event_mgr, &machine->loop); if (rc == -1) { mm_loop_shutdown(&machine->loop); mm_scheduler_free(&machine->scheduler); mm_free(machine); return -1; } rc = mm_signalmgr_init(&machine->signal_mgr, &machine->loop); if (rc == -1) { mm_eventmgr_free(&machine->event_mgr, &machine->loop); mm_loop_shutdown(&machine->loop); mm_scheduler_free(&machine->scheduler); mm_free(machine); return -1; } mm_machinemgr_add(&machinarium.machine_mgr, machine); rc = mm_thread_create(&machine->thread, 0, machine_main, machine); if (rc == -1) { mm_machinemgr_delete(&machinarium.machine_mgr, machine); mm_eventmgr_free(&machine->event_mgr, &machine->loop); mm_loop_shutdown(&machine->loop); mm_scheduler_free(&machine->scheduler); mm_free(machine); return -1; } return machine->id; } MACHINE_API int machine_wait(uint64_t machine_id) { mm_machine_t *machine; machine = mm_machinemgr_delete_by_id(&machinarium.machine_mgr, machine_id); if (machine == NULL) { return -1; } int rc; rc = mm_thread_join(&machine->thread); if (machine->name) { mm_free(machine->name); } mm_wait_flag_destroy(machine->join_flag); mm_free(machine); return rc; } int machine_wait_nb(uint64_t machine_id) { mm_machine_t *machine; machine = mm_machinemgr_delete_by_id(&machinarium.machine_mgr, machine_id); if (machine == NULL) { return -1; } int rc; rc = mm_wait_flag_wait(machine->join_flag, UINT32_MAX); mm_thread_join(&machine->thread); if (machine->name) { mm_free(machine->name); } mm_wait_flag_destroy(machine->join_flag); mm_free(machine); return rc; } MACHINE_API int machine_stop(uint64_t machine_id) { mm_machine_t *machine; machine = mm_machinemgr_delete_by_id(&machinarium.machine_mgr, machine_id); if (machine == NULL) { return -1; } atomic_store(&machine->online, 0); return 0; } MACHINE_API int machine_active(void) { return atomic_load(&mm_self->online); } MACHINE_API uint64_t machine_self(void) { return mm_self->id; } MACHINE_API void **machine_thread_private(void) { return &(mm_self->thread_global_private); } MACHINE_API void machine_stop_current(void) { atomic_store(&mm_self->online, 0); } void machine_set_global_dtor(void (*dtor)(void *gl)) { mm_self->global_dtor = dtor; } enum { MM_CORO_SYSTEM = 1 << 0, }; static inline mm_coroutine_t * mm_coroutine_create_internal(machine_coroutine_t function, void *arg, const char *name, int flags) { mm_errno_set(0); mm_coroutine_cache_t *cache; if (flags & MM_CORO_SYSTEM) { cache = &mm_self->system_coroutine_cache; } else { cache = &mm_self->general_coroutine_cache; } mm_coroutine_t *coroutine; coroutine = mm_coroutine_cache_pop(cache); if (coroutine == NULL) { mm_errno_set(ENOMEM); return NULL; } mm_scheduler_new(&mm_self->scheduler, coroutine, function, arg); mm_coroutine_set_name(coroutine, name); return coroutine; } MACHINE_API int64_t machine_coroutine_create(machine_coroutine_t function, void *arg) { mm_coroutine_t *coroutine; coroutine = mm_coroutine_create_internal(function, arg, NULL, 0); if (coroutine == NULL) { return -1; } return coroutine->id; } MACHINE_API int64_t machine_coroutine_create_named(machine_coroutine_t function, void *arg, const char *name) { mm_coroutine_t *coroutine; coroutine = mm_coroutine_create_internal(function, arg, name, 0); if (coroutine == NULL) { return -1; } return coroutine->id; } MACHINE_API int64_t machine_coroutine_create_system( machine_coroutine_t function, void *arg, const char *name) { mm_coroutine_t *coroutine; coroutine = mm_coroutine_create_internal(function, arg, name, MM_CORO_SYSTEM); if (coroutine == NULL) { return -1; } return coroutine->id; } MACHINE_API void machine_sleep(uint32_t time_ms) { mm_errno_set(0); mm_call_t call; mm_call(&call, MM_CALL_SLEEP, time_ms); } MACHINE_API int machine_join(uint64_t coroutine_id) { mm_errno_set(0); if (coroutine_id == mm_self->scheduler.current->id) { mm_errno_set(EINVAL); return -1; } mm_coroutine_t *coroutine; coroutine = mm_scheduler_find(&mm_self->scheduler, coroutine_id); if (coroutine == NULL) { mm_errno_set(ENOENT); return -1; } mm_coroutine_t *waiter; waiter = mm_scheduler_current(&mm_self->scheduler); mm_scheduler_join(coroutine, waiter); mm_scheduler_yield(&mm_self->scheduler); return 0; } MACHINE_API int machine_cancel(uint64_t coroutine_id) { mm_errno_set(0); mm_coroutine_t *coroutine; coroutine = mm_scheduler_find(&mm_self->scheduler, coroutine_id); if (coroutine == NULL) { mm_errno_set(ENOENT); return -1; } mm_coroutine_cancel(coroutine); return 0; } MACHINE_API const char *machine_coroutine_get_name(uint64_t coroutine_id) { mm_errno_set(0); mm_coroutine_t *coroutine; coroutine = mm_scheduler_find(&mm_self->scheduler, coroutine_id); if (coroutine == NULL) { mm_errno_set(ENOENT); return NULL; } return mm_coroutine_get_name(coroutine); } MACHINE_API int machine_cancelled(void) { mm_coroutine_t *coroutine; coroutine = mm_scheduler_current(&mm_self->scheduler); if (coroutine == NULL) { return -1; } return coroutine->cancel > 0; } MACHINE_API int machine_timedout(void) { return mm_errno_get() == ETIMEDOUT; } MACHINE_API int machine_errno(void) { return mm_errno_get(); } MACHINE_API int machine_errno_retryable(int errno_) { return errno_ == EAGAIN || errno_ == EWOULDBLOCK || errno_ == EINTR; } /* See also accept(2) */ MACHINE_API int machine_accept_errno_retryable(int errno_) { if (machine_errno_retryable(errno_)) { return 1; } switch (errno_) { case ENETDOWN: case EPROTO: case ENOPROTOOPT: case EHOSTDOWN: #ifdef ENONET case ENONET: #endif case EHOSTUNREACH: case EOPNOTSUPP: case ENETUNREACH: return 1; default: return 0; } } MACHINE_API uint64_t machine_time_ms(void) { mm_clock_update(&mm_self->loop.clock); return mm_self->loop.clock.time_ms; } MACHINE_API uint64_t machine_time_us(void) { mm_clock_update(&mm_self->loop.clock); return mm_self->loop.clock.time_us; } MACHINE_API int64_t machine_timeofday_sec(void) { mm_clock_update(&mm_self->loop.clock); return mm_self->loop.clock.time_sec; } MACHINE_API void machine_stat(uint64_t *coroutine_count, uint64_t *coroutine_cache_count, uint64_t *msg_allocated, uint64_t *msg_cache_count, uint64_t *msg_cache_gc_count, uint64_t *msg_cache_size) { uint64_t general_coroutine_count = 0; uint64_t general_coroutine_cache_count = 0; uint64_t system_coroutine_count = 0; uint64_t system_coroutine_cache_count = 0; mm_coroutine_cache_stat(&mm_self->general_coroutine_cache, &general_coroutine_count, &general_coroutine_cache_count); mm_coroutine_cache_stat(&mm_self->system_coroutine_cache, &system_coroutine_count, &system_coroutine_cache_count); *coroutine_count = general_coroutine_count + system_coroutine_count; *coroutine_cache_count = general_coroutine_cache_count + system_coroutine_cache_count; mm_msgcache_stat(&mm_self->msg_cache, msg_allocated, msg_cache_gc_count, msg_cache_count, msg_cache_size); } void mm_machine_atexit(void (*fun)(void *), void *arg) { mm_machine_t *s = mm_self; if (s->natexit == MM_MACHINE_MAX_ATEXIT) { abort(); } mm_machine_atexit_entry_t *e = &s->atexit[s->natexit++]; e->fptr = fun; e->arg = arg; } odyssey-1.6.0-rc1/sources/machinarium/machine_mgr.c000066400000000000000000000033111526064202200222720ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include void mm_machinemgr_init(mm_machinemgr_t *mgr) { mm_spinlock_init(&mgr->lock); mm_list_init(&mgr->list); mgr->count = 0; mgr->seq = 0; } void mm_machinemgr_free(mm_machinemgr_t *mgr) { mm_spinlock_destroy(&mgr->lock); } int mm_machinemgr_count(mm_machinemgr_t *mgr) { int count; mm_spinlock_lock(&mgr->lock); count = mgr->count; mm_spinlock_unlock(&mgr->lock); return count; } void mm_machinemgr_add(mm_machinemgr_t *mgr, mm_machine_t *machine) { mm_spinlock_lock(&mgr->lock); machine->id = mgr->seq++; mm_list_append(&mgr->list, &machine->link); mgr->count++; mm_spinlock_unlock(&mgr->lock); } void mm_machinemgr_delete(mm_machinemgr_t *mgr, mm_machine_t *machine) { mm_spinlock_lock(&mgr->lock); mm_list_unlink(&machine->link); mgr->count--; mm_spinlock_unlock(&mgr->lock); } mm_machine_t *mm_machinemgr_delete_by_id(mm_machinemgr_t *mgr, uint64_t id) { mm_spinlock_lock(&mgr->lock); mm_list_t *i; mm_list_foreach (&mgr->list, i) { mm_machine_t *machine; machine = mm_container_of(i, mm_machine_t, link); if (machine->id == id) { mm_list_unlink(&machine->link); mgr->count--; mm_spinlock_unlock(&mgr->lock); return machine; } } mm_spinlock_unlock(&mgr->lock); return NULL; } mm_machine_t *mm_machinemgr_find_by_id(mm_machinemgr_t *mgr, uint64_t id) { mm_spinlock_lock(&mgr->lock); mm_list_t *i; mm_list_foreach (&mgr->list, i) { mm_machine_t *machine; machine = mm_container_of(i, mm_machine_t, link); if (machine->id == id) { mm_spinlock_unlock(&mgr->lock); return machine; } } mm_spinlock_unlock(&mgr->lock); return NULL; } odyssey-1.6.0-rc1/sources/machinarium/memory.c000066400000000000000000000071731526064202200213430ustar00rootroot00000000000000#include #include #include #include #if defined(USE_TCMALLOC) || defined(USE_TCMALLOC_PROFILE) #include #define MALLOC_IMPL tc_malloc #define FREE_IMPL tc_free #define REALLOC_IMPL tc_realloc #define CALLOC_IMPL tc_calloc #else #define MALLOC_IMPL malloc #define FREE_IMPL free #define REALLOC_IMPL realloc #define CALLOC_IMPL calloc #endif /* USE_TCMALLOC || USE_TCMALLOC_PROFILE */ #ifndef MM_MEM_PROF static inline void *wrap_allocation(void *d) { if (mm_likely(d != NULL)) { return d; } if (mm_self != NULL) { mm_errno_set(ENOMEM); } return NULL; } void *mm_malloc(size_t size) { void *mem = MALLOC_IMPL(size); return wrap_allocation(mem); } void mm_free(void *ptr) { FREE_IMPL(ptr); } void *mm_calloc(size_t nmemb, size_t size) { void *mem = CALLOC_IMPL(nmemb, size); return wrap_allocation(mem); } void *mm_realloc(void *ptr, size_t size) { void *mem = REALLOC_IMPL(ptr, size); return wrap_allocation(mem); } #else typedef struct { uint64_t size; } mm_alloc_header_t; static inline void *wrap_allocation(void *d) { if (mm_likely(d != NULL)) { return ((mm_alloc_header_t *)d) + 1; } if (mm_self != NULL) { mm_errno_set(ENOMEM); } return NULL; } static inline void *unwrap_profiled_ptr(void *d) { if (d != NULL) { return ((mm_alloc_header_t *)d) - 1; } return NULL; } static inline void *malloc_internal(size_t size, int set_zero) { void *mem = MALLOC_IMPL(sizeof(mm_alloc_header_t) + size); if (mem != NULL) { mm_alloc_header_t *hdr = mem; hdr->size = size; if (set_zero) { memset(hdr + 1, 0, size); } if (mm_self != NULL) { mm_coroutine_t *coro = mm_self->scheduler.current; if (coro != NULL) { coro->allocated_bytes += size; } else { mm_self->allocated_bytes += size; } } } return wrap_allocation(mem); } void *mm_malloc(size_t size) { return malloc_internal(size, 0 /* set_zero */); } void mm_free(void *ptr) { mm_alloc_header_t *hdr = unwrap_profiled_ptr(ptr); if (hdr != NULL) { if (mm_self != NULL) { mm_coroutine_t *coro = mm_self->scheduler.current; if (coro != NULL) { coro->freed_bytes += hdr->size; } else { mm_self->freed_bytes += hdr->size; } } FREE_IMPL(hdr); } } void *mm_realloc(void *ptr, size_t size) { if (ptr == NULL) { return malloc_internal(size, 0 /* set_zero */); } if (size == 0) { mm_free(ptr); return NULL; } mm_alloc_header_t *hdr = unwrap_profiled_ptr(ptr); uint64_t old_size = hdr->size; mm_alloc_header_t *new_hdr = REALLOC_IMPL(hdr, sizeof(mm_alloc_header_t) + size); if (new_hdr != NULL) { new_hdr->size = size; if (mm_self != NULL) { mm_coroutine_t *coro = mm_self->scheduler.current; if (coro != NULL) { coro->freed_bytes += old_size; coro->allocated_bytes += size; } else { mm_self->freed_bytes += old_size; mm_self->allocated_bytes += size; } } } else { /* nothing should be accounted */ } return wrap_allocation(new_hdr); } void *mm_calloc(size_t nmemb, size_t size) { if (nmemb != 0 && size > SIZE_MAX / nmemb) { if (mm_self != NULL) { mm_errno_set(ENOMEM); } return NULL; } return malloc_internal(nmemb * size, 1 /* set_zero */); } #endif /* ifndef MM_MEM_PROF */ char *mm_strdup(const char *s) { size_t len = strlen(s) + 1; void *new = mm_malloc(len); if (new == NULL) { return NULL; } return (char *)memcpy(new, s, len); } char *mm_strndup(const char *s, size_t n) { size_t len = strnlen(s, n); char *new = (char *)mm_malloc(len + 1); if (new == NULL) { return NULL; } new[len] = '\0'; return (char *)memcpy(new, s, len); } odyssey-1.6.0-rc1/sources/machinarium/mm.c000066400000000000000000000052351526064202200204410ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include static int machinarium_stack_size = 0; static int machinarium_system_stack_size = 0; static int machinarium_pool_size = 0; static int machinarium_coroutine_cache_size = 0; static int machinarium_msg_cache_gc_size = 0; static int machinarium_msg_cache_gc_count = 0; static int machinarium_initialized = 0; static int machinarium_dns_ttl_ms = -1; mm_t machinarium; static inline size_t machinarium_page_size(void) { return sysconf(_SC_PAGESIZE); } MACHINE_API void machinarium_set_stack_size(int size) { machinarium_stack_size = size; } MACHINE_API void machinarium_set_system_stack_size(int size) { machinarium_system_stack_size = size; } MACHINE_API void machinarium_set_pool_size(int size) { machinarium_pool_size = size; } MACHINE_API void machinarium_set_coroutine_cache_size(int size) { machinarium_coroutine_cache_size = size; } MACHINE_API void machinarium_set_msg_cache_gc_size(int size) { machinarium_msg_cache_gc_size = size; } MACHINE_API void machinarium_set_msg_cache_gc_count(int count) { machinarium_msg_cache_gc_count = count; } MACHINE_API void machinarium_set_dns_ttl_ms(int ttl_ms) { machinarium_dns_ttl_ms = ttl_ms; } MACHINE_API int machinarium_init(void) { if (machinarium_initialized) { return -1; } if (machinarium_stack_size <= 0) { machinarium_stack_size = 4; } if (machinarium_system_stack_size <= 0) { machinarium_system_stack_size = 32; } if (machinarium_pool_size == 0) { machinarium_pool_size = 1; } if (machinarium_dns_ttl_ms < 0) { machinarium_dns_ttl_ms = 30 * 1000; } machinarium.config.page_size = machinarium_page_size(); machinarium.config.stack_size = machinarium_stack_size; machinarium.config.system_stack_size = machinarium_system_stack_size; machinarium.config.pool_size = machinarium_pool_size; machinarium.config.coroutine_cache_size = machinarium_coroutine_cache_size; machinarium.config.msg_cache_gc_size = machinarium_msg_cache_gc_size; machinarium.config.msg_cache_gc_count = machinarium_msg_cache_gc_count; machinarium.config.dns_ttl_ms = machinarium_dns_ttl_ms; mm_machinemgr_init(&machinarium.machine_mgr); mm_tls_engine_init(); mm_taskmgr_init(&machinarium.task_mgr); mm_taskmgr_start(&machinarium.task_mgr, machinarium.config.pool_size, machinarium.config.dns_ttl_ms); machinarium_initialized = 1; return 0; } MACHINE_API void machinarium_free(void) { if (!machinarium_initialized) { return; } mm_taskmgr_stop(&machinarium.task_mgr); mm_machinemgr_free(&machinarium.machine_mgr); mm_tls_engine_free(); machinarium_initialized = 0; } odyssey-1.6.0-rc1/sources/machinarium/msg.c000066400000000000000000000054221526064202200206140ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include MACHINE_API machine_msg_t *machine_msg_create(int reserve) { mm_msg_t *msg = mm_msgcache_pop(&mm_self->msg_cache); if (msg == NULL) { return NULL; } msg->type = 0; if (reserve > 0) { int rc; rc = mm_buf_ensure(&msg->data, reserve); if (rc == -1) { mm_msg_unref(&mm_self->msg_cache, msg); return NULL; } mm_buf_advance(&msg->data, reserve); } return (machine_msg_t *)msg; } MACHINE_API machine_msg_t *machine_msg_create_or_advance(machine_msg_t *obj, int size) { if (obj == NULL) { return machine_msg_create(size); } mm_msg_t *msg = mm_cast(mm_msg_t *, obj); int rc; rc = mm_buf_ensure(&msg->data, size); if (rc == -1) { return NULL; } mm_buf_advance(&msg->data, size); return obj; } MACHINE_API inline void machine_msg_free(machine_msg_t *obj) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); mm_msgcache_push(&mm_self->msg_cache, msg); } MACHINE_API void machine_msg_free_safe(machine_msg_t *obj) { if (obj != NULL) { machine_msg_free(obj); } } MACHINE_API inline void machine_msg_set_type(machine_msg_t *obj, int type) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); msg->type = type; } MACHINE_API inline int machine_msg_type(machine_msg_t *obj) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); return msg->type; } MACHINE_API inline void *machine_msg_data(machine_msg_t *obj) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); return msg->data.start; } MACHINE_API int machine_msg_size(machine_msg_t *obj) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); return mm_buf_used(&msg->data); } MACHINE_API void machine_msg_reset(machine_msg_t *obj) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); mm_buf_reset(&msg->data); } MACHINE_API int machine_msg_shrink(machine_msg_t *obj, int size) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); return mm_buf_shrink(&msg->data, size); } MACHINE_API int machine_msg_write(machine_msg_t *obj, void *buf, int size) { mm_msg_t *msg = mm_cast(mm_msg_t *, obj); int rc; if (buf == NULL) { rc = mm_buf_ensure(&msg->data, size); if (rc == -1) { return -1; } mm_buf_advance(&msg->data, size); return 0; } rc = mm_buf_add(&msg->data, buf, size); return rc; } MACHINE_API machine_msg_t *machine_msg_copy(machine_msg_t *msg) { machine_msg_t *copy = machine_msg_create(machine_msg_size(msg)); if (copy == NULL) { return NULL; } char *dst = machine_msg_data(copy); char *src = machine_msg_data(msg); int n = machine_msg_size(msg); memcpy(dst, src, n); return copy; } MACHINE_API struct iovec machine_msg_iovec(machine_msg_t *msg) { struct iovec vec; vec.iov_base = machine_msg_data(msg); vec.iov_len = machine_msg_size(msg); return vec; } odyssey-1.6.0-rc1/sources/machinarium/msg_cache.c000066400000000000000000000035471526064202200217450ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include void mm_msgcache_init(mm_msgcache_t *cache) { mm_list_init(&cache->list); cache->count = 0; cache->count_allocated = 0; cache->count_gc = 0; cache->size = 0; cache->gc_size_watermark = 0; cache->gc_count_watermark = 0; } void mm_msgcache_free(mm_msgcache_t *cache) { mm_list_t *i, *n; mm_list_foreach_safe (&cache->list, i, n) { mm_msg_t *msg = mm_container_of(i, mm_msg_t, link); mm_buf_free(&msg->data); mm_free(msg); } } void mm_msgcache_stat(mm_msgcache_t *cache, uint64_t *count_allocated, uint64_t *count_gc, uint64_t *count, uint64_t *size) { *count_allocated = cache->count_allocated; *count_gc = cache->count_gc; *count = cache->count; *size = cache->size; } mm_msg_t *mm_msgcache_pop(mm_msgcache_t *cache) { mm_msg_t *msg = NULL; if (cache->count > 0) { mm_list_t *first = mm_list_pop(&cache->list); cache->count--; msg = mm_container_of(first, mm_msg_t, link); cache->size -= mm_buf_size(&msg->data); goto init; } cache->count_allocated++; msg = mm_malloc(sizeof(mm_msg_t)); if (msg == NULL) { return NULL; } mm_buf_init(&msg->data); /* fallthrough */ init: msg->machine_id = mm_self->id; msg->refs = 0; msg->type = 0; mm_buf_reset(&msg->data); mm_list_init(&msg->link); return msg; } void mm_msgcache_push(mm_msgcache_t *cache, mm_msg_t *msg) { int too_many = cache->count >= (uint64_t)cache->gc_count_watermark; int too_large = mm_buf_size(&msg->data) > cache->gc_size_watermark; int not_own = msg->machine_id != mm_self->id; if (not_own || too_many || too_large) { cache->count_gc++; mm_buf_free(&msg->data); mm_free(msg); return; } mm_list_append(&cache->list, &msg->link); cache->count++; cache->size += mm_buf_size(&msg->data); } odyssey-1.6.0-rc1/sources/machinarium/mutex.c000066400000000000000000000037651526064202200212000ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include enum { MM_MUTEX_UNLOCKED = 0, MM_MUTEX_LOCKED = 1, }; void mm_mutex_init(mm_mutex_t *mutex) { atomic_init(&mutex->state, MM_MUTEX_UNLOCKED); mutex->owner_machine = NULL; mutex->owner_coro_id = MM_SLEEPY_NO_CORO_ID; mm_wait_list_init(&mutex->wl, &mutex->state); } mm_mutex_t *mm_mutex_create(void) { mm_mutex_t *mutex = mm_malloc(sizeof(mm_mutex_t)); if (mutex == NULL) { return NULL; } mm_mutex_init(mutex); return mutex; } void mm_mutex_destroy(mm_mutex_t *mutex) { /* TODO: maybe handle not empty queue somehow? */ mm_wait_list_destroy(&mutex->wl); } void mm_mutex_free(mm_mutex_t *mutex) { mm_mutex_destroy(mutex); mm_free(mutex); } int mm_mutex_lock(mm_mutex_t *mutex, uint32_t timeout_ms) { if (mm_self == NULL || mm_self->scheduler.current == NULL) { abort(); } uint64_t start_ms = machine_time_ms(); while (1) { uint_fast64_t expected = MM_MUTEX_UNLOCKED; if (atomic_compare_exchange_strong(&mutex->state, &expected, MM_MUTEX_LOCKED)) { mutex->owner_machine = (void *)mm_self; mutex->owner_coro_id = mm_self->scheduler.current->id; return 1; } uint64_t elapsed = machine_time_ms() - start_ms; if (elapsed >= timeout_ms) { return 0; } uint32_t remaining = timeout_ms - (uint32_t)elapsed; int rc = mm_wait_list_compare_wait(&mutex->wl, NULL, MM_MUTEX_LOCKED, remaining); if (rc == -1 && mm_errno_get() == ECANCELED) { return 0; } /* all other scenarios leads to retry */ } return 0; } int mm_mutex_lock2(mm_mutex_t *mutex) { return mm_mutex_lock(mutex, UINT32_MAX); } void mm_mutex_unlock(mm_mutex_t *mutex) { if (mm_self == NULL || mm_self->scheduler.current == NULL) { abort(); } mutex->owner_machine = NULL; mutex->owner_coro_id = MM_SLEEPY_NO_CORO_ID; atomic_store(&mutex->state, MM_MUTEX_UNLOCKED); mm_wait_list_notify(&mutex->wl); } odyssey-1.6.0-rc1/sources/machinarium/read.c000066400000000000000000000035001526064202200207340ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include MACHINE_API ssize_t machine_read_raw(mm_io_t *obj, void *buf, size_t size) { mm_io_t *io = mm_cast(mm_io_t *, obj); #ifdef MM_BUILD_COMPRESSION /* If streaming compression is enabled then use correspondent compression * read function. */ if (mm_compression_is_active(io)) { return mm_zpq_read(io->zpq_stream, buf, size); } #endif return mm_io_read(io, buf, size); } MACHINE_API int machine_read_pending(mm_io_t *io) { return mm_io_read_pending(io); } static inline int machine_read_to(mm_io_t *io, machine_msg_t *msg, size_t size, uint32_t time_ms) { mm_errno_set(0); if (!io->attached) { mm_errno_set(ENOTCONN); return -1; } int rc; int offset = machine_msg_size(msg); rc = machine_msg_write(msg, NULL, size); if (rc == -1) { return -1; } char *dest = (char *)machine_msg_data(msg) + offset; size_t total = 0; while (total != size) { int rc = machine_read_raw(io, dest + total, size - total); if (rc > 0) { total += rc; continue; } /* error or eof */ if (rc == -1) { int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { rc = mm_io_wait(io, time_ms); if (rc == -1) { return -1; } continue; } } return -1; } return 0; } MACHINE_API machine_msg_t *machine_read(mm_io_t *obj, size_t size, uint32_t time_ms) { mm_errno_set(0); machine_msg_t *msg = machine_msg_create(0); if (msg == NULL) { return NULL; } int rc = machine_read_to(obj, msg, size, time_ms); if (rc == -1) { machine_msg_free(msg); return NULL; } return msg; } odyssey-1.6.0-rc1/sources/machinarium/ring_buffer.c000066400000000000000000000137111526064202200223160ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include /* * yet another ring buffer impl */ mm_ring_buffer_t *mm_ring_buffer_create(size_t capacity) { size_t full_size = sizeof(mm_ring_buffer_t) + capacity * sizeof(uint8_t); mm_ring_buffer_t *rbuf = mm_malloc(full_size); if (rbuf == NULL) { mm_errno_set(ENOMEM); return NULL; } memset(rbuf, 0, full_size); rbuf->capacity = capacity; return rbuf; } void mm_ring_buffer_free(mm_ring_buffer_t *rbuf) { mm_free(rbuf); } size_t mm_ring_buffer_read(mm_ring_buffer_t *rbuf, void *out, size_t count) { if (count == 0 || rbuf->size == 0) { return 0; } if (count > rbuf->size) { count = rbuf->size; } size_t tail_len = rbuf->capacity - rbuf->rpos; if (count <= tail_len) { /* * enough to read from rpos to right, up to capacity * situations like: * * | tail_len | * [ . . a a a a a a a a a . . . . . . ] * { count } * ^ ^ * rpos wpos * * | tail_len | * [ q q . . . a a a a a a a a a a a a a a a ] * { count } * ^ ^ * wpos rpos * */ memcpy(out, rbuf->data + rbuf->rpos, count); rbuf->rpos = (rbuf->rpos + count) % rbuf->capacity; } else { /* * need to read tail from rpos and up to end of data * and some head at the begining * situations like: * * | tail_len | * [ q q q q . . a a a a a a a a a a a a a a a ] * ---> } { count ---> * ^ ^ * wpos rpos * * | tail_len | * [ q q q q q q a a a a a a a a a a a a a a a ] * ---> } { count ---> * ^ * rpos/wpos */ memcpy(out, rbuf->data + rbuf->rpos, tail_len); memcpy(((uint8_t *)out) + tail_len, rbuf->data, count - tail_len); rbuf->rpos = count - tail_len; } rbuf->size -= count; return count; } size_t mm_ring_buffer_drain(mm_ring_buffer_t *rbuf, size_t count) { /* same as read() but doesn't copy any bytes */ if (count == 0 || rbuf->size == 0) { return 0; } if (count > rbuf->size) { count = rbuf->size; } size_t tail_len = rbuf->capacity - rbuf->rpos; if (count <= tail_len) { rbuf->rpos = (rbuf->rpos + count) % rbuf->capacity; } else { rbuf->rpos = count - tail_len; } rbuf->size -= count; return count; } size_t mm_ring_buffer_write(mm_ring_buffer_t *rbuf, const void *data, size_t count) { size_t free = mm_ring_buffer_available(rbuf); if (count == 0 || free == 0) { return 0; } if (count > free) { count = free; } size_t tail_len = rbuf->capacity - rbuf->wpos; if (count <= tail_len) { /* * need to write at the tail only * * | tail_len | * [ . . . a a a a a a a a . . . . . . . ] * ^ ^ * rpos wpos */ memcpy(rbuf->data + rbuf->wpos, data, count); rbuf->wpos = (rbuf->wpos + count) % rbuf->capacity; } else { /* * need to write at the tail and then at some head * * | count - tail_len | | tail_len | * [ . . . . . . . . . . . . a a a a a a a a . . . . . . . ] * ^ ^ * rpos wpos */ memcpy(rbuf->data + rbuf->wpos, data, tail_len); memcpy(rbuf->data, ((uint8_t *)data) + tail_len, count - tail_len); rbuf->wpos = count - tail_len; } rbuf->size += count; return count; } size_t mm_ring_buffer_size(const mm_ring_buffer_t *rbuf) { return rbuf->size; } size_t mm_ring_buffer_capacity(const mm_ring_buffer_t *rbuf) { return rbuf->capacity; } size_t mm_ring_buffer_available(const mm_ring_buffer_t *rbuf) { return rbuf->capacity - rbuf->size; } void mm_ring_buffer_clear(mm_ring_buffer_t *rbuf) { rbuf->size = 0; rbuf->wpos = 0; rbuf->rpos = 0; } int mm_ring_buffer_is_full(const mm_ring_buffer_t *rbuf) { return rbuf->capacity == rbuf->size; } MACHINE_API machine_ring_buffer_t *machine_ring_buffer_create(size_t capacity) { mm_ring_buffer_t *rbuf = mm_ring_buffer_create(capacity); if (rbuf != NULL) { return mm_cast(machine_ring_buffer_t *, rbuf); } return NULL; } MACHINE_API void machine_ring_buffer_free(machine_ring_buffer_t *rbuf) { mm_ring_buffer_t *rb = mm_cast(mm_ring_buffer_t *, rbuf); mm_ring_buffer_free(rb); } MACHINE_API size_t machine_ring_buffer_read(machine_ring_buffer_t *rbuf, void *out, size_t count) { mm_ring_buffer_t *rb = mm_cast(mm_ring_buffer_t *, rbuf); return mm_ring_buffer_read(rb, out, count); } MACHINE_API size_t machine_ring_buffer_drain(machine_ring_buffer_t *rbuf, size_t count) { mm_ring_buffer_t *rb = mm_cast(mm_ring_buffer_t *, rbuf); return mm_ring_buffer_drain(rb, count); } MACHINE_API size_t machine_ring_buffer_write(machine_ring_buffer_t *rbuf, const void *data, size_t count) { mm_ring_buffer_t *rb = mm_cast(mm_ring_buffer_t *, rbuf); return mm_ring_buffer_write(rb, data, count); } MACHINE_API size_t machine_ring_buffer_size(const machine_ring_buffer_t *rbuf) { const mm_ring_buffer_t *rb = mm_cast(const mm_ring_buffer_t *, rbuf); return mm_ring_buffer_size(rb); } MACHINE_API size_t machine_ring_buffer_capacity(const machine_ring_buffer_t *rbuf) { const mm_ring_buffer_t *rb = mm_cast(const mm_ring_buffer_t *, rbuf); return mm_ring_buffer_capacity(rb); } MACHINE_API size_t machine_ring_buffer_available(const machine_ring_buffer_t *rbuf) { const mm_ring_buffer_t *rb = mm_cast(const mm_ring_buffer_t *, rbuf); return mm_ring_buffer_available(rb); } MACHINE_API int machine_ring_buffer_is_full(const machine_ring_buffer_t *rbuf) { const mm_ring_buffer_t *rb = mm_cast(const mm_ring_buffer_t *, rbuf); return mm_ring_buffer_is_full(rb); } odyssey-1.6.0-rc1/sources/machinarium/scheduler.c000066400000000000000000000113401526064202200220000ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #ifdef HAVE_TSAN #include #endif static void mm_scheduler_main(void *arg) { mm_coroutine_t *coroutine = arg; mm_scheduler_t *scheduler = &mm_self->scheduler; mm_scheduler_set(scheduler, coroutine, MM_CACTIVE); coroutine->function(coroutine->function_arg); /* wakeup joiners */ mm_list_t *i; mm_list_foreach (&coroutine->joiners, i) { mm_coroutine_t *joiner; joiner = mm_container_of(i, mm_coroutine_t, link_join); mm_scheduler_set(scheduler, joiner, MM_CREADY); } /* Mark coroutine free and ready to be put back in * cache for reuse. * * Yet this can not be done here, because yield expects previous * coroutine stack to be available. */ mm_scheduler_set(scheduler, coroutine, MM_CFREE); #ifdef HAVE_TSAN coroutine->context.destroying = 1; #endif mm_scheduler_yield(scheduler); } int mm_scheduler_init(mm_scheduler_t *scheduler) { mm_list_init(&scheduler->list_ready); mm_list_init(&scheduler->list_active); scheduler->id_seq = 0; scheduler->count_ready = 0; scheduler->count_active = 0; mm_coroutine_init(&scheduler->main); scheduler->current = &scheduler->main; return 0; } void mm_scheduler_free(mm_scheduler_t *scheduler) { mm_coroutine_t *coroutine; mm_list_t *i, *p; mm_list_foreach_safe (&scheduler->list_ready, i, p) { coroutine = mm_container_of(i, mm_coroutine_t, link); mm_coroutine_free(coroutine); } mm_list_foreach_safe (&scheduler->list_active, i, p) { coroutine = mm_container_of(i, mm_coroutine_t, link); mm_coroutine_free(coroutine); } } void mm_scheduler_run(mm_scheduler_t *scheduler) { while (scheduler->count_ready > 0) { mm_coroutine_t *coroutine; coroutine = mm_container_of(scheduler->list_ready.next, mm_coroutine_t, link); mm_scheduler_set(&mm_self->scheduler, coroutine, MM_CACTIVE); mm_scheduler_call(&mm_self->scheduler, coroutine); if (coroutine->state == MM_CFREE) { mm_coroutine_cache_push(coroutine->origin_cache, coroutine); } } } void mm_scheduler_new(mm_scheduler_t *scheduler, mm_coroutine_t *coroutine, mm_function_t function, void *arg) { mm_list_init(&coroutine->link); mm_list_init(&coroutine->link_join); mm_list_init(&coroutine->joiners); coroutine->cancel = 0; coroutine->id = scheduler->id_seq++; coroutine->function = function; coroutine->function_arg = arg; mm_context_create(&coroutine->context, &coroutine->stack, mm_scheduler_main, coroutine); #ifdef HAVE_TSAN char name[256]; snprintf(name, sizeof(name), "Machine ID: %" PRIu64 "; coroutine ID: %" PRIu64, mm_self->id, coroutine->id); __tsan_set_fiber_name(coroutine->context.tsan_fiber, name); #endif mm_scheduler_set(scheduler, coroutine, MM_CREADY); } mm_coroutine_t *mm_scheduler_find(mm_scheduler_t *scheduler, uint64_t id) { mm_coroutine_t *coroutine; mm_list_t *i; mm_list_foreach (&scheduler->list_ready, i) { coroutine = mm_container_of(i, mm_coroutine_t, link); if (coroutine->id == id) { return coroutine; } } mm_list_foreach (&scheduler->list_active, i) { coroutine = mm_container_of(i, mm_coroutine_t, link); if (coroutine->id == id) { return coroutine; } } return NULL; } void mm_scheduler_set(mm_scheduler_t *scheduler, mm_coroutine_t *coroutine, mm_coroutinestate_t state) { if (coroutine->state == state) { return; } switch (coroutine->state) { case MM_CNEW: case MM_CFREE: break; case MM_CREADY: scheduler->count_ready--; break; case MM_CACTIVE: scheduler->count_active--; break; } mm_list_t *target = NULL; switch (state) { case MM_CNEW: case MM_CFREE: break; case MM_CREADY: target = &scheduler->list_ready; scheduler->count_ready++; break; case MM_CACTIVE: target = &scheduler->list_active; scheduler->count_active++; break; } mm_list_unlink(&coroutine->link); mm_list_init(&coroutine->link); if (target != NULL) { mm_list_append(target, &coroutine->link); } coroutine->state = state; } void mm_scheduler_call(mm_scheduler_t *scheduler, mm_coroutine_t *coroutine) { mm_coroutine_t *resume = scheduler->current; assert(resume != NULL); coroutine->resume = resume; scheduler->current = coroutine; mm_context_swap(&resume->context, &coroutine->context); } void mm_scheduler_yield(mm_scheduler_t *scheduler) { mm_coroutine_t *current = scheduler->current; mm_coroutine_t *resume = current->resume; assert(resume != NULL); scheduler->current = resume; mm_context_swap(¤t->context, &resume->context); } void mm_scheduler_join(mm_coroutine_t *coroutine, mm_coroutine_t *joiner) { mm_list_append(&coroutine->joiners, &joiner->link_join); } odyssey-1.6.0-rc1/sources/machinarium/sem.c000066400000000000000000000026201526064202200206070ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include void mm_sem_init(mm_sem_t *sem, uint64_t value) { atomic_init(&sem->value, value); sem->initial_value = value; mm_wait_list_init(&sem->notifier, &sem->value); } void mm_sem_destroy(mm_sem_t *sem) { mm_wait_list_destroy(&sem->notifier); } int mm_sem_wait(mm_sem_t *sem) { return mm_sem_timedwait(sem, UINT32_MAX); } int mm_sem_timedwait(mm_sem_t *sem, uint32_t timeout_ms) { uint64_t start_ms = machine_time_ms(); uint64_t end_ms = start_ms + timeout_ms; int infinite = timeout_ms == UINT32_MAX; while (1) { uint64_t val = atomic_load(&sem->value); if (val == 0) { uint64_t now_ms = machine_time_ms(); if (!infinite && now_ms >= end_ms) { mm_errno_set(ETIMEDOUT); return -1; } uint64_t left = infinite ? UINT32_MAX : end_ms - now_ms; int rc = mm_wait_list_compare_wait(&sem->notifier, NULL, 0, (uint32_t)left); if (rc == -1 && mm_errno_get() != EAGAIN) { return rc; } continue; } if (atomic_compare_exchange_weak(&sem->value, &val, val - 1)) { break; } } return 0; } void mm_sem_post(mm_sem_t *sem) { uint64_t v = atomic_fetch_add(&sem->value, 1); if (mm_unlikely(v == UINT64_MAX)) { abort(); } mm_wait_list_notify(&sem->notifier); } odyssey-1.6.0-rc1/sources/machinarium/signal_mgr.c000066400000000000000000000123571526064202200221550ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #if defined(__linux__) #include #else #include #endif #include #include #include #include #include static void mm_signalmgr_on_read(mm_fd_t *handle) { mm_signalmgr_t *mgr = handle->on_read_arg; /* do one-time wakeup and detach all readers */ mm_list_t *i, *n; mm_list_foreach_safe (&mgr->readers, i, n) { mm_signalrd_t *reader; reader = mm_container_of(i, mm_signalrd_t, link); mm_scheduler_wakeup(&mm_self->scheduler, reader->call.coroutine); mm_list_unlink(&reader->link); } } #if defined(__linux__) int mm_signalmgr_init(mm_signalmgr_t *mgr, mm_loop_t *loop) { mm_list_init(&mgr->readers); memset(&mgr->fd, 0, sizeof(mgr->fd)); mgr->fd.fd = -1; sigset_t mask; sigemptyset(&mask); int rc; rc = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); if (rc == -1) { return -1; } mgr->fd.fd = rc; rc = mm_loop_add_ro(loop, &mgr->fd, mm_signalmgr_on_read, mgr); if (rc == -1) { close(mgr->fd.fd); mgr->fd.fd = -1; return -1; } return 0; } void mm_signalmgr_free(mm_signalmgr_t *mgr, mm_loop_t *loop) { if (mgr->fd.fd == -1) { return; } mm_loop_delete(loop, &mgr->fd); close(mgr->fd.fd); mgr->fd.fd = -1; } int mm_signalmgr_set(mm_signalmgr_t *mgr, sigset_t *set, sigset_t *ignore) { int rc; rc = signalfd(mgr->fd.fd, set, SFD_NONBLOCK | SFD_CLOEXEC); if (rc == -1) { return -1; } assert(rc == mgr->fd.fd); sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_UNBLOCK, &mask, NULL); pthread_sigmask(SIG_BLOCK, set, NULL); pthread_sigmask(SIG_BLOCK, ignore, NULL); return 0; } int mm_signalmgr_wait(mm_signalmgr_t *mgr, uint32_t time_ms) { mm_errno_set(0); while (1) { struct signalfd_siginfo fdsi; memset(&fdsi, 0, sizeof(fdsi)); int rc = read(mgr->fd.fd, &fdsi, sizeof(fdsi)); if (rc == sizeof(fdsi)) { return fdsi.ssi_signo; } int err = errno; if (machine_errno_retryable(err)) { mm_signalrd_t reader; mm_list_init(&reader.link); mm_list_append(&mgr->readers, &reader.link); mm_call(&reader.call, MM_CALL_SIGNAL, time_ms); if (reader.call.status != 0) { /* timedout or cancel */ if (!reader.signal) { mm_list_unlink(&reader.link); } return -1; } continue; } mm_errno_set(err); return -1; } abort(); } #else static volatile int mm_signalmgr_write_fd = -1; static void mm_signalmgr_handler(int signo) { if (mm_signalmgr_write_fd != -1) { uint8_t byte = (uint8_t)signo; ssize_t rc = write(mm_signalmgr_write_fd, &byte, 1); (void)rc; } } int mm_signalmgr_init(mm_signalmgr_t *mgr, mm_loop_t *loop) { mm_list_init(&mgr->readers); memset(&mgr->fd, 0, sizeof(mgr->fd)); mgr->fd.fd = -1; mgr->write_fd = -1; int fds[2]; if (pipe(fds) == -1) { return -1; } fcntl(fds[0], F_SETFD, FD_CLOEXEC); fcntl(fds[1], F_SETFD, FD_CLOEXEC); fcntl(fds[0], F_SETFL, O_NONBLOCK); fcntl(fds[1], F_SETFL, O_NONBLOCK); mgr->fd.fd = fds[0]; mgr->write_fd = fds[1]; int rc; rc = mm_loop_add_ro(loop, &mgr->fd, mm_signalmgr_on_read, mgr); if (rc == -1) { close(mgr->fd.fd); close(mgr->write_fd); mgr->fd.fd = -1; mgr->write_fd = -1; return -1; } mm_signalmgr_write_fd = mgr->write_fd; return 0; } void mm_signalmgr_free(mm_signalmgr_t *mgr, mm_loop_t *loop) { if (mgr->fd.fd == -1) { return; } mm_loop_delete(loop, &mgr->fd); close(mgr->fd.fd); close(mgr->write_fd); mgr->fd.fd = -1; mgr->write_fd = -1; mm_signalmgr_write_fd = -1; } int mm_signalmgr_set(mm_signalmgr_t *mgr, sigset_t *set, sigset_t *ignore) { (void)mgr; /* * make sure none of the requested signals stay blocked, so that * our handler actually gets invoked on delivery */ sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_UNBLOCK, &mask, NULL); int sig; for (sig = 1; sig < NSIG; sig++) { if (set && sigismember(set, sig)) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = mm_signalmgr_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(sig, &sa, NULL) == -1) { /* * some signals (e.g. SIGKILL/SIGSTOP) * cannot be handled, ignore failure */ } } if (ignore && sigismember(ignore, sig)) { signal(sig, SIG_IGN); } } return 0; } int mm_signalmgr_wait(mm_signalmgr_t *mgr, uint32_t time_ms) { mm_errno_set(0); while (1) { uint8_t byte = 0; int rc = read(mgr->fd.fd, &byte, sizeof(byte)); if (rc == sizeof(byte)) { return (int)byte; } int err = errno; if (machine_errno_retryable(err)) { mm_signalrd_t reader; mm_list_init(&reader.link); mm_list_append(&mgr->readers, &reader.link); mm_call(&reader.call, MM_CALL_SIGNAL, time_ms); if (reader.call.status != 0) { /* timedout or cancel */ if (!reader.signal) { mm_list_unlink(&reader.link); } return -1; } continue; } mm_errno_set(err); return -1; } abort(); } #endif MACHINE_API int machine_signal_init(sigset_t *set, sigset_t *ignore) { return mm_signalmgr_set(&mm_self->signal_mgr, set, ignore); } MACHINE_API int machine_signal_wait(uint32_t time_ms) { return mm_signalmgr_wait(&mm_self->signal_mgr, time_ms); } odyssey-1.6.0-rc1/sources/machinarium/socket.c000066400000000000000000000152321526064202200213160ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #if defined(__linux__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) #define MM_TCP_KEEPIDLE TCP_KEEPIDLE #elif defined(__APPLE__) #define MM_TCP_KEEPIDLE TCP_KEEPALIVE #elif defined(TCP_KEEPIDLE) #define MM_TCP_KEEPIDLE TCP_KEEPIDLE #endif int mm_socket(int domain, int type, int protocol) { /* get and return file descriptor of env socket */ int fd; #if defined(SOCK_CLOEXEC) fd = socket(domain, type | SOCK_CLOEXEC, protocol); #else fd = socket(domain, type, protocol); if (fd != -1) { fcntl(fd, F_SETFD, FD_CLOEXEC); } #endif return fd; } #if defined(__linux__) int mm_socket_eventfd(unsigned int initval, int *write_fd) { int fd = eventfd(initval, EFD_NONBLOCK | EFD_CLOEXEC); if (fd == -1) { return -1; } if (write_fd) { *write_fd = fd; } return fd; } #else int mm_socket_eventfd(unsigned int initval, int *write_fd) { int fds[2]; #if defined(__APPLE__) if (pipe(fds) == -1) { return -1; } fcntl(fds[0], F_SETFD, FD_CLOEXEC); fcntl(fds[1], F_SETFD, FD_CLOEXEC); fcntl(fds[0], F_SETFL, O_NONBLOCK); fcntl(fds[1], F_SETFL, O_NONBLOCK); #else if (pipe2(fds, O_CLOEXEC | O_NONBLOCK) == -1) { return -1; } #endif for (unsigned int i = 0; i < initval; i++) { char byte = 1; write(fds[1], &byte, 1); } if (write_fd) { *write_fd = fds[1]; } return fds[0]; } #endif int mm_socket_set_nonblock(int fd, int enable) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { return -1; } if (enable) { flags |= O_NONBLOCK; } else { flags &= ~O_NONBLOCK; } int rc; rc = fcntl(fd, F_SETFL, flags); return rc; } int mm_socket_set_nodelay(int fd, int enable) { int rc; rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); return rc; } int mm_socket_set_keepalive(int fd, int enable, int delay, int interval, int keep_count, int usr_timeout) { int rc; rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); if (rc == MM_NOTOK_RETCODE) { return MM_NOTOK_RETCODE; } if (enable) { rc = setsockopt(fd, IPPROTO_TCP, MM_TCP_KEEPIDLE, &delay, sizeof(delay)); if (rc == MM_NOTOK_RETCODE) { return MM_NOTOK_RETCODE; } rc = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); if (rc == MM_NOTOK_RETCODE) { return MM_NOTOK_RETCODE; } rc = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_count, sizeof(keep_count)); if (rc == MM_NOTOK_RETCODE) { return MM_NOTOK_RETCODE; } #if defined(TCP_USER_TIMEOUT) rc = setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &usr_timeout, sizeof(usr_timeout)); if (rc == MM_NOTOK_RETCODE) { return MM_NOTOK_RETCODE; } #else (void)usr_timeout; #endif } return MM_OK_RETCODE; } int mm_socket_advice_keepalive_usr_timeout(int delay, int interval, int keep_count) { /* * https://habr.com/ru/articles/700470/ * delay, interval are in seconds * usr timeout in milliseconds * see man 7 tcp */ return 1000 * (delay + interval * keep_count) - 500; } int mm_socket_set_nosigpipe(int fd, int enable) { #if defined(SO_NOSIGPIPE) int rc; rc = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)); if (rc == -1) { return -1; } (void)fd; (void)enable; return 0; #else (void)fd; (void)enable; return 0; #endif (void)fd; (void)enable; return 0; } int mm_socket_set_reuseaddr(int fd, int enable) { int rc; #ifdef SO_REUSEADDR rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); #else /* ignore reuse addr in case of not enable */ rc = enable ? -1 : 0; #endif return rc; } int mm_socket_set_reuseport(int fd, int enable) { int rc; #ifdef SO_REUSEPORT rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)); #else /* ignore reuse port in case of not enable */ rc = enable ? -1 : 0; #endif return rc; } int mm_socket_set_nolinger(int fd) { int rc; struct linger linger = { .l_linger = 0, .l_onoff = 1 }; rc = setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); return rc; } int mm_socket_set_ipv6only(int fd, int enable) { int rc; rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(enable)); return rc; } int mm_socket_error(int fd) { int error; socklen_t errorsize = sizeof(error); int rc; rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorsize); if (rc == -1) { return -1; } return error; } int mm_socket_connect(int fd, struct sockaddr *sa) { int addrlen; if (sa->sa_family == AF_INET) { addrlen = sizeof(struct sockaddr_in); } else if (sa->sa_family == AF_INET6) { addrlen = sizeof(struct sockaddr_in6); } else if (sa->sa_family == AF_UNIX) { addrlen = sizeof(struct sockaddr_un); } else { errno = EINVAL; return -1; } int rc; rc = connect(fd, sa, addrlen); return rc; } int mm_socket_bind(int fd, struct sockaddr *sa) { int addrlen; if (sa->sa_family == AF_INET) { addrlen = sizeof(struct sockaddr_in); } else if (sa->sa_family == AF_INET6) { addrlen = sizeof(struct sockaddr_in6); } else if (sa->sa_family == AF_UNIX) { addrlen = sizeof(struct sockaddr_un); } else { errno = EINVAL; return -1; } int rc; rc = bind(fd, sa, addrlen); return rc; } int mm_socket_listen(int fd, int backlog) { int rc; rc = listen(fd, backlog); return rc; } int mm_socket_accept(int fd, struct sockaddr *sa, socklen_t *slen) { int rc; #if defined(__linux__) rc = accept4(fd, sa, slen, SOCK_CLOEXEC | SOCK_NONBLOCK); #else rc = accept(fd, sa, slen); if (rc != -1) { fcntl(rc, F_SETFD, FD_CLOEXEC); mm_socket_set_nonblock(rc, 1); } #endif return rc; } int mm_socket_write(int fd, const void *buf, int size) { int rc; rc = write(fd, buf, size); return rc; } int mm_socket_writev(int fd, const struct iovec *iov, int iovc) { int rc; rc = writev(fd, iov, iovc); return rc; } int mm_socket_read(int fd, void *buf, int size) { int rc; rc = read(fd, buf, size); return rc; } int mm_socket_read_pending(int fd) { int nbytes; int rc = ioctl(fd, FIONREAD, &nbytes); if (rc == -1) { return -1; } return nbytes > 0; } int mm_socket_getsockname(int fd, struct sockaddr *sa, socklen_t *salen) { int rc; rc = getsockname(fd, sa, salen); return rc; } int mm_socket_getpeername(int fd, struct sockaddr *sa, socklen_t *salen) { int rc; rc = getpeername(fd, sa, salen); return rc; } int mm_socket_getaddrinfo(char *node, char *service, struct addrinfo *hints, struct addrinfo **res) { int rc; rc = getaddrinfo(node, service, hints, res); return rc; } odyssey-1.6.0-rc1/sources/machinarium/stat.c000066400000000000000000000007241526064202200210010ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include MACHINE_API int machine_io_sysfd(mm_io_t *obj) { mm_io_t *io = mm_cast(mm_io_t *, obj); return io->fd; } MACHINE_API int machine_io_mask(mm_io_t *obj) { mm_io_t *io = mm_cast(mm_io_t *, obj); return io->handle.mask; } MACHINE_API int machine_io_mm_fd(mm_io_t *obj) { mm_io_t *io = mm_cast(mm_io_t *, obj); return io->handle.fd; } odyssey-1.6.0-rc1/sources/machinarium/task_mgr.c000066400000000000000000000057041526064202200216400ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include enum { MM_TASK, MM_TASK_EXIT }; static void mm_taskmgr_main(void *arg) { mm_taskmgr_worker_t *worker = arg; sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_BLOCK, &mask, NULL); mm_list_init(&worker->dns_cache); for (;;) { mm_msg_t *msg; msg = mm_channel_read(&machinarium.task_mgr.channel, UINT32_MAX); assert(msg != NULL); if (msg->type == MM_TASK_EXIT) { mm_free(msg); break; } assert(msg->type == MM_TASK); assert(mm_buf_used(&msg->data) == sizeof(mm_task_t)); mm_task_t *task; task = (mm_task_t *)msg->data.start; task->function(worker, task->arg); int event_mgr_fd; event_mgr_fd = mm_eventmgr_signal(&task->on_complete); if (event_mgr_fd > 0) { mm_eventmgr_wakeup(event_mgr_fd); } } mm_list_t *i, *s; mm_list_foreach_safe (&worker->dns_cache, i, s) { mm_dns_cache_entry_t *e; e = mm_container_of(i, mm_dns_cache_entry_t, link); mm_free_dns_cache_entry(e); } } void mm_taskmgr_init(mm_taskmgr_t *mgr) { mgr->workers_count = 0; mgr->workers = NULL; mm_channel_init(&mgr->channel); } int mm_taskmgr_start(mm_taskmgr_t *mgr, int workers_count, uint64_t dns_ttl_ms) { mgr->workers_count = workers_count; mgr->workers = mm_malloc(sizeof(mm_taskmgr_worker_t) * workers_count); if (mgr->workers == NULL) { return -1; } int i = 0; for (; i < workers_count; i++) { char name[32]; mm_snprintf(name, sizeof(name), "resolver: %d", i); mgr->workers[i].dns_ttl_ms = dns_ttl_ms; mgr->workers[i].id = machine_create(name, mm_taskmgr_main, &mgr->workers[i]); } return 0; } void mm_taskmgr_stop(mm_taskmgr_t *mgr) { int i; int rc; for (i = 0; i < mgr->workers_count; i++) { mm_msg_t *msg; msg = mm_malloc(sizeof(mm_msg_t)); if (msg == NULL) { /* todo: */ abort(); return; } mm_msg_init(msg, MM_TASK_EXIT); mm_channel_write(&mgr->channel, msg); } for (i = 0; i < mgr->workers_count; i++) { rc = machine_wait(mgr->workers[i].id); if (rc != MM_OK_RETCODE) { /* TODO: handle gracefully */ abort(); return; } } mm_channel_free(&mgr->channel); mm_free(mgr->workers); } int mm_taskmgr_new(mm_taskmgr_t *mgr, mm_task_function_t function, void *arg, uint32_t time_ms) { mm_msg_t *msg; msg = (mm_msg_t *)machine_msg_create(sizeof(mm_task_t)); if (msg == NULL) { return -1; } msg->type = MM_TASK; mm_task_t *task; task = (mm_task_t *)msg->data.start; task->function = function; task->arg = arg; mm_eventmgr_add(&mm_self->event_mgr, &task->on_complete); /* schedule task */ mm_channel_write(&mgr->channel, msg); /* wait for completion */ time_ms = UINT32_MAX; int ready; ready = mm_eventmgr_wait(&mm_self->event_mgr, &task->on_complete, time_ms); if (!ready) { /* todo: */ abort(); return 0; } machine_msg_free((machine_msg_t *)msg); return 0; } odyssey-1.6.0-rc1/sources/machinarium/thread.c000066400000000000000000000022501526064202200212710ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include int mm_thread_create(mm_thread_t *thread, size_t stack_size, mm_thread_function_t function, void *arg) { pthread_attr_t attr; int rc; rc = pthread_attr_init(&attr); if (rc != 0) { return -1; } if (stack_size != 0) { rc = pthread_attr_setstacksize(&attr, stack_size); if (rc != 0) { pthread_attr_destroy(&attr); return -1; } } thread->function = function; thread->arg = arg; rc = pthread_create(&thread->id, &attr, function, arg); pthread_attr_destroy(&attr); if (rc != 0) { return -1; } return 0; } int mm_thread_join(mm_thread_t *thread) { int rc; rc = pthread_join(thread->id, NULL); return rc; } int mm_thread_set_name(char *name) { int rc; #if defined(__linux__) || defined(__FreeBSD__) rc = pthread_setname_np(pthread_self(), name); #else rc = pthread_setname_np(name); #endif return rc; } int mm_thread_disable_cancel(void) { int unused; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &unused); return pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &unused); } odyssey-1.6.0-rc1/sources/machinarium/tls.c000066400000000000000000000464121526064202200206340ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef OD_ENABLE_SSL_KEYLOG #include #include #endif #if !USE_BORINGSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L) static pthread_mutex_t *mm_tls_locks = NULL; static void mm_tls_lock_thread_id(CRYPTO_THREADID *tid) { CRYPTO_THREADID_set_numeric(tid, (unsigned long)pthread_self()); } static void mm_tls_lock_callback(int mode, int type, const char *file, int line) { (void)file; (void)line; if (mode & CRYPTO_LOCK) { pthread_mutex_lock(&mm_tls_locks[type]); } else { pthread_mutex_unlock(&mm_tls_locks[type]); } } static void mm_tls_lock_init(void) { int size = CRYPTO_num_locks() * sizeof(pthread_mutex_t); mm_tls_locks = OPENSSL_malloc(size); if (mm_tls_locks == NULL) { abort(); return; } int i = 0; for (; i < CRYPTO_num_locks(); i++) { pthread_mutex_init(&mm_tls_locks[i], NULL); } CRYPTO_THREADID_set_callback(mm_tls_lock_thread_id); CRYPTO_set_locking_callback(mm_tls_lock_callback); } static void mm_tls_lock_free(void) { CRYPTO_set_locking_callback(NULL); int i = 0; for (; i < CRYPTO_num_locks(); i++) { pthread_mutex_destroy(&mm_tls_locks[i]); } OPENSSL_free(mm_tls_locks); } #endif void mm_tls_engine_init(void) { #ifdef MM_MEM_PROF CRYPTO_set_mem_functions(mm_malloc_crypto, mm_realloc_crypto, mm_free_crypto); #endif SSL_library_init(); SSL_load_error_strings(); #if !USE_BORINGSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L) mm_tls_lock_init(); #endif } void mm_tls_engine_free(void) { #if !USE_BORINGSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L) mm_tls_lock_free(); ERR_remove_state(getpid()); #endif /* SSL_COMP_free_compression_methods(); */ /*FIPS_mode_set(0);*/ #if !USE_BORINGSSL ENGINE_cleanup(); #ifndef OPENSSL_IS_AWSLC CONF_modules_unload(1); #endif #endif /* !USE_BORINGSSL */ #ifndef OPENSSL_IS_AWSLC EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); #endif ERR_free_strings(); } void mm_tls_init(mm_io_t *io) { (void)io; } void mm_tls_free(mm_io_t *io) { if (io->tls_ssl) { SSL_free(io->tls_ssl); } } void mm_tls_error_reset(mm_io_t *io) { mm_errno_set(0); io->tls_error = 0; io->tls_error_msg[0] = 0; } static inline char *mm_tls_strerror(int error) { switch (error) { case SSL_ERROR_NONE: return "SSL_ERROR_NONE"; case SSL_ERROR_SSL: return "SSL_ERROR_SSL"; case SSL_ERROR_WANT_CONNECT: return "SSL_ERROR_CONNECT"; case SSL_ERROR_WANT_ACCEPT: return "SSL_ERROR_ACCEPT"; case SSL_ERROR_WANT_READ: return "SSL_ERROR_WANT_READ"; case SSL_ERROR_WANT_WRITE: return "SSL_ERROR_WANT_WRITE"; case SSL_ERROR_WANT_X509_LOOKUP: return "SSL_ERROR_WANT_X509_LOOKUP"; case SSL_ERROR_SYSCALL: return "SSL_ERROR_SYSCALL"; case SSL_ERROR_ZERO_RETURN: return "SSL_ERROR_ZERO_RETURN"; } return "SSL_ERROR unknown"; } static inline void mm_tls_error(mm_io_t *io, int ssl_rc, char *fmt, ...) { /* * Use OpenSSL error description or strerror if needed * See https://docs.openssl.org/master/man3/SSL_get_error/#return-values */ unsigned int error; error = SSL_get_error(io->tls_ssl, ssl_rc); unsigned int error_peek; char *error_str; error_str = "unknown error"; error_peek = ERR_get_error(); if (error_peek > 0) { error_str = ERR_error_string(error_peek, NULL); } else if (ssl_rc == 0) { error_str = "unexpected EOF (connection reset)"; } else if (ssl_rc < 0) { int errno_ = mm_errno_get(); if (errno_ != 0) { error_str = strerror(errno_); } else { error_str = "no bio underlying error (client closed the connection?)"; } } /* error message */ va_list args; va_start(args, fmt); int len = 0; len = mm_vsnprintf(io->tls_error_msg, sizeof(io->tls_error_msg), fmt, args); va_end(args); len += mm_snprintf(io->tls_error_msg + len, sizeof(io->tls_error_msg) - len, ": %s: %s", mm_tls_strerror(error), error_str); io->tls_error = 1; if (errno == 0) { errno = EIO; } } #ifdef OD_ENABLE_SSL_KEYLOG static void keylog_cb(const SSL *ssl, const char *line) { (void)ssl; static FILE *file = NULL; static int failed = 0; static pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mu); if (failed) { goto to_return; } if (file == NULL) { const char *path = getenv("SSLKEYLOGFILE"); if (path == NULL || path[0] == '\0') { failed = 1; goto to_return; } int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd == -1) { failed = 1; goto to_return; } file = fdopen(fd, "a"); file = fopen(path, "a"); if (file == NULL) { failed = 1; goto to_return; } } fprintf(file, "%s\n", line); fflush(file); to_return: pthread_mutex_unlock(&mu); } #endif /* OD_ENABLE_SSL_KEYLOG */ static atomic_uint_fast64_t mm_tls_cache_epoch = 0; void machine_tls_cache_invalidate(void) { atomic_fetch_add_explicit(&mm_tls_cache_epoch, 1, memory_order_relaxed); } void mm_tls_ctx_free(mm_tls_ctx_t *ctx_container) { if (ctx_container->protocols) { mm_free(ctx_container->protocols); } if (ctx_container->cert_file) { mm_free(ctx_container->cert_file); } if (ctx_container->key_file) { mm_free(ctx_container->key_file); } if (ctx_container->ca_file) { mm_free(ctx_container->ca_file); } if (ctx_container->ca_path) { mm_free(ctx_container->ca_path); } if (ctx_container->tls_ctx) { SSL_CTX_free(ctx_container->tls_ctx); } mm_free(ctx_container); } static inline int mm_tls_str_match(const char *a, const char *b) { if (a == NULL || b == NULL) { return a == b; } return strcmp(a, b) == 0; } static inline int mm_tls_opts_match(const mm_tls_ctx_t *entry, const mm_tls_t *tls) { return entry->verify == tls->verify && mm_tls_str_match(entry->protocols, tls->protocols) && mm_tls_str_match(entry->cert_file, tls->cert_file) && mm_tls_str_match(entry->key_file, tls->key_file) && mm_tls_str_match(entry->ca_file, tls->ca_file) && mm_tls_str_match(entry->ca_path, tls->ca_path); } SSL_CTX *mm_tls_get_context(mm_io_t *io, int is_client) { mm_tls_ctx_t **ctx_list; if (is_client) { ctx_list = &mm_self->client_tls_ctx; } else { ctx_list = &mm_self->server_tls_ctx; } uint64_t epoch = atomic_load_explicit(&mm_tls_cache_epoch, memory_order_relaxed); mm_tls_ctx_t *prev = NULL; mm_tls_ctx_t *it = *ctx_list; while (it != NULL) { mm_tls_ctx_t *next = it->next; if (it->epoch != epoch) { if (prev) { prev->next = next; } else { *ctx_list = next; } mm_tls_ctx_free(it); it = next; continue; } if (mm_tls_opts_match(it, io->tls)) { return it->tls_ctx; } prev = it; it = next; } /* Cached context not found - we must create ctx */ SSL_CTX *ctx; const SSL_METHOD *ssl_method = NULL; if (is_client) { ssl_method = SSLv23_client_method(); } else { ssl_method = SSLv23_server_method(); } ctx = SSL_CTX_new(ssl_method); if (ctx == NULL) { return NULL; } #ifdef OD_ENABLE_SSL_KEYLOG SSL_CTX_set_keylog_callback(ctx, keylog_cb); #endif SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); /* verify mode */ int verify = 0; switch (io->tls->verify) { case MM_TLS_NONE: verify = SSL_VERIFY_NONE; break; case MM_TLS_PEER: verify = SSL_VERIFY_PEER; break; case MM_TLS_PEER_STRICT: verify = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; break; } SSL_CTX_set_verify(ctx, verify, NULL); SSL_CTX_set_verify_depth(ctx, 6); /* cert file */ int rc; if (io->tls->cert_file) { rc = SSL_CTX_use_certificate_chain_file(ctx, io->tls->cert_file); if (!rc) { mm_tls_error(io, 0, "SSL_CTX_use_certificate_chain_file()"); goto error; } } /* key file */ if (io->tls->key_file) { rc = SSL_CTX_use_PrivateKey_file(ctx, io->tls->key_file, SSL_FILETYPE_PEM); if (rc != 1) { mm_tls_error(io, 0, "SSL_CTX_use_PrivateKey_file()"); goto error; } } if (io->tls->cert_file && io->tls->key_file) { rc = SSL_CTX_check_private_key(ctx); if (rc != 1) { mm_tls_error(io, 0, "SSL_CTX_check_private_key()"); goto error; } } /* ca file and ca_path */ if (io->tls->ca_file || io->tls->ca_path) { rc = SSL_CTX_load_verify_locations(ctx, io->tls->ca_file, io->tls->ca_path); if (rc != 1) { mm_tls_error(io, 0, "SSL_CTX_load_verify_locations()"); goto error; } } /* ocsp */ /* set ciphers */ const char cipher_list[] = "ALL:!aNULL:!eNULL"; rc = SSL_CTX_set_cipher_list(ctx, cipher_list); if (rc != 1) { mm_tls_error(io, 0, "SSL_CTX_set_cipher_list()"); goto error; } if (!is_client) { unsigned char sid[SSL_MAX_SSL_SESSION_ID_LENGTH]; if (!RAND_bytes(sid, sizeof(sid))) { mm_tls_error(io, 0, "failed to generate session id"); goto error; } if (!SSL_CTX_set_session_id_context(ctx, sid, sizeof(sid))) { mm_tls_error(io, 0, "failed to set session id context"); goto error; } SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); } /* * Place new ctx on top of the cache, keyed by TLS options * content. The originating mm_tls_t object may be freed with * its connection, the copies below keep the cache valid. */ mm_tls_ctx_t *ctx_container = mm_malloc(sizeof(*ctx_container)); if (ctx_container == NULL) { goto error; } memset(ctx_container, 0, sizeof(*ctx_container)); ctx_container->verify = io->tls->verify; ctx_container->epoch = epoch; ctx_container->tls_ctx = ctx; int dup_rc = 0; if (io->tls->protocols) { ctx_container->protocols = mm_strdup(io->tls->protocols); dup_rc = ctx_container->protocols == NULL; } if (!dup_rc && io->tls->cert_file) { ctx_container->cert_file = mm_strdup(io->tls->cert_file); dup_rc = ctx_container->cert_file == NULL; } if (!dup_rc && io->tls->key_file) { ctx_container->key_file = mm_strdup(io->tls->key_file); dup_rc = ctx_container->key_file == NULL; } if (!dup_rc && io->tls->ca_file) { ctx_container->ca_file = mm_strdup(io->tls->ca_file); dup_rc = ctx_container->ca_file == NULL; } if (!dup_rc && io->tls->ca_path) { ctx_container->ca_path = mm_strdup(io->tls->ca_path); dup_rc = ctx_container->ca_path == NULL; } if (dup_rc) { mm_errno_set(ENOMEM); mm_tls_ctx_free(ctx_container); return NULL; } ctx_container->next = *ctx_list; *ctx_list = ctx_container; return ctx; error: if (ctx) { SSL_CTX_free(ctx); } return NULL; } static int mm_tls_prepare(mm_io_t *io, int is_client) { SSL *ssl = NULL; int rc; SSL_CTX *ctx = mm_tls_get_context(io, is_client); if (ctx == NULL) { goto error; } ssl = SSL_new(ctx); if (ssl == NULL) { mm_tls_error(io, 0, "SSL_new()"); goto error; } /* set server name */ if (io->tls->server) { rc = SSL_set_tlsext_host_name(ssl, io->tls->server); if (rc != 1) { mm_tls_error(io, 0, "SSL_set_tlsext_host_name()"); goto error; } } /* set socket */ rc = SSL_set_rfd(ssl, io->fd); if (rc == -1) { mm_tls_error(io, 0, "SSL_set_rfd()"); goto error; } rc = SSL_set_wfd(ssl, io->fd); if (rc == -1) { mm_tls_error(io, 0, "SSL_set_wfd()"); goto error; } io->tls_ssl = ssl; return 0; error: if (ssl) { SSL_free(ssl); } return -1; } static inline int mm_tls_verify_name(char *cert_name, const char *name) { const char *cert_domain, *domain, *next_dot; if (strcasecmp(cert_name, name) == 0) { return 0; } /* wildcard match */ if (cert_name[0] != '*') { return -1; } /* * valid wildcards: * - "*.domain.tld" * - "*.sub.domain.tld" * - etc. * reject "*.tld". * no attempt to prevent the use of eg. "*.co.uk". */ cert_domain = &cert_name[1]; /* disallow "*" */ if (cert_domain[0] == '\0') { return -1; } /* disallow "*foo" */ if (cert_domain[0] != '.') { return -1; } /* disallow "*.." */ if (cert_domain[1] == '.') { return -1; } next_dot = strchr(&cert_domain[1], '.'); /* disallow "*.bar" */ if (next_dot == NULL) { return -1; } /* disallow "*.bar.." */ if (next_dot[1] == '.') { return -1; } domain = strchr(name, '.'); /* no wildcard match against a name with no host part. */ if (name[0] == '.') { return -1; } /* no wildcard match against a name with no domain part. */ if (domain == NULL || strlen(domain) == 1) { return -1; } if (strcasecmp(cert_domain, domain) == 0) { return 0; } return -1; } int mm_tls_verify_common_name(mm_io_t *io, char *name) { X509 *cert = NULL; #if !USE_BORINGSSL && (OPENSSL_VERSION_NUMBER >= 0x40000000) const X509_NAME *subject_name = NULL; #else X509_NAME *subject_name = NULL; #endif const X509_NAME_ENTRY *entry = NULL; const ASN1_STRING *cn_asn1 = NULL; unsigned char *common_name = NULL; int common_name_len = 0; int idx; cert = SSL_get_peer_certificate(io->tls_ssl); if (cert == NULL) { mm_tls_error(io, 0, "SSL_get_peer_certificate()"); return -1; } subject_name = X509_get_subject_name(cert); if (subject_name == NULL) { mm_tls_error(io, 0, "X509_get_subject_name()"); goto error; } idx = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1); if (idx < 0) { mm_tls_error(io, 0, "X509_NAME_get_index_by_NID()"); goto error; } entry = X509_NAME_get_entry(subject_name, idx); if (entry == NULL) { mm_tls_error(io, 0, "X509_NAME_get_entry()"); goto error; } cn_asn1 = X509_NAME_ENTRY_get_data(entry); if (cn_asn1 == NULL) { mm_tls_error(io, 0, "X509_NAME_ENTRY_get_data()"); goto error; } common_name_len = ASN1_STRING_to_UTF8(&common_name, cn_asn1); if (common_name_len < 0 || common_name == NULL) { mm_tls_error(io, 0, "ASN1_STRING_to_UTF8()"); goto error; } /* validate name */ if (common_name_len != (int)strlen((char *)common_name)) { mm_tls_error( io, 0, "NUL byte in Common Name field, probably a malicious " "server certificate"); goto error; } if (mm_tls_verify_name((char *)common_name, name) == -1) { mm_tls_error(io, 0, "bad common name: %s (expected %s)", common_name, name); goto error; } X509_free(cert); OPENSSL_free(common_name); return 0; error: X509_free(cert); if (common_name) { OPENSSL_free(common_name); } return -1; } int mm_tls_handshake(mm_io_t *io, uint32_t timeout) { mm_tls_error_reset(io); int is_client = !io->accepted; int rc; rc = mm_tls_prepare(io, is_client); if (rc == -1) { return -1; } mm_io_set_deadline(io, timeout); while (1) { if (io->accepted) { rc = SSL_accept(io->tls_ssl); } else if (io->connected) { rc = SSL_connect(io->tls_ssl); } if (rc > 0) { /* success */ break; } int error = SSL_get_error(io->tls_ssl, rc); if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { rc = mm_io_wait_deadline(io); if (rc == -1) { return -1; } continue; } if (io->connected) { mm_tls_error(io, rc, "SSL_connect()"); } else { mm_tls_error(io, rc, "SSL_accept()"); } return -1; } if (is_client) { if (io->tls->server) { rc = mm_tls_verify_common_name(io, io->tls->server); if (rc == -1) { return -1; } } long verify_result = SSL_get_verify_result(io->tls_ssl); if (verify_result != X509_V_OK) { mm_tls_error( io, 0, "SSL_get_verify_result(): %ld (%s)", verify_result, X509_verify_cert_error_string(verify_result)); return -1; } } return 0; } int mm_tls_write(mm_io_t *io, const char *buf, int size) { mm_tls_error_reset(io); io->want = 0; int rc; rc = SSL_write(io->tls_ssl, buf, size); if (rc > 0) { return rc; } int error = SSL_get_error(io->tls_ssl, rc); if (error == SSL_ERROR_WANT_READ) { io->want |= MM_IO_WANT_READ; errno = EAGAIN; return -1; } if (error == SSL_ERROR_WANT_WRITE) { io->want |= MM_IO_WANT_WRITE; errno = EAGAIN; return -1; } mm_tls_error(io, rc, "SSL_write()"); return -1; } int mm_tls_writev(mm_io_t *io, const struct iovec *iov, int n) { /* * https://docs.openssl.org/1.1.1/man3/SSL_write/#notes * * odyssey uses SSL_MODE_ENABLE_PARTIAL_WRITE, to be sure * no tls write will block the worker thread for long time * * in case of attemting write large packet, extra malloc * for buffer and bytes copies will be performed, so we * must truncate the write size up to maximum partial block * write size, which is 16KB according to OpenSSL docs */ #define OPENSSL_MAX_PARTIAL_BLOCK_SIZE (16 * 1024) mm_tls_error_reset(io); int size = mm_iov_size_of(iov, n); if (size > OPENSSL_MAX_PARTIAL_BLOCK_SIZE) { size = OPENSSL_MAX_PARTIAL_BLOCK_SIZE; } static MM_THREAD_LOCAL char buffer[OPENSSL_MAX_PARTIAL_BLOCK_SIZE]; mm_iovncpy(buffer, iov, size, n); io->want = 0; int rc; rc = SSL_write(io->tls_ssl, buffer, size); if (rc > 0) { return rc; } int error = SSL_get_error(io->tls_ssl, rc); if (error == SSL_ERROR_WANT_READ) { io->want |= MM_IO_WANT_READ; errno = EAGAIN; return -1; } if (error == SSL_ERROR_WANT_WRITE) { io->want |= MM_IO_WANT_WRITE; errno = EAGAIN; return -1; } mm_tls_error(io, rc, "SSL_write()"); return -1; } int mm_tls_get_cert_hash(mm_io_t *io, unsigned char (*cert_hash)[MM_CERT_HASH_LEN], unsigned int *len) { mm_tls_error_reset(io); X509 *server_cert; const EVP_MD *algo_type = NULL; /* size for SHA-512 */ int algo_nid; *len = 0; server_cert = SSL_get_certificate(io->tls_ssl); if (server_cert == NULL) { return -1; } /* * Get the signature algorithm of the certificate to determine the hash * algorithm to use for the result. */ if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert), &algo_nid, NULL)) { mm_tls_error(io, -1, "OBJ_find_sigid_algs(X509_get_signature_nid)"); /* elog(ERROR, "could not determine server certificate signature algorithm");*/ return -1; } /* * The TLS server's certificate bytes need to be hashed with SHA-256 if * its signature algorithm is MD5 or SHA-1 as per RFC 5929 * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else * is used, the same hash as the signature algorithm is used. */ switch (algo_nid) { case NID_md5: case NID_sha1: algo_type = EVP_sha256(); break; default: algo_type = EVP_get_digestbynid(algo_nid); if (algo_type == NULL) { mm_tls_error(io, -1, "OBJ_nid2sn(algo_nid)"); /* elog(ERROR, "could not find digest for NID %s", OBJ_nid2sn(algo_nid)); */ return -1; } break; } /* generate and save the certificate hash */ if (!X509_digest(server_cert, algo_type, *cert_hash, len)) { mm_tls_error(io, -1, "X509_digest()"); /* elog(ERROR, "could not generate server certificate hash"); */ return -1; } return 0; } int mm_tls_read_pending(mm_io_t *io) { return SSL_pending(io->tls_ssl) > 0; } int mm_tls_read(mm_io_t *io, char *buf, int size) { mm_tls_error_reset(io); io->want = 0; int rc; rc = SSL_read(io->tls_ssl, buf, size); if (rc > 0) { return rc; } int error = SSL_get_error(io->tls_ssl, rc); if (error == SSL_ERROR_WANT_READ) { io->want |= MM_IO_WANT_READ; errno = EAGAIN; return -1; } if (error == SSL_ERROR_WANT_WRITE) { io->want |= MM_IO_WANT_WRITE; errno = EAGAIN; return -1; } if (error == SSL_ERROR_ZERO_RETURN) { return 0; } mm_tls_error(io, rc, "SSL_read()"); return -1; } odyssey-1.6.0-rc1/sources/machinarium/wait_flag.c000066400000000000000000000046321526064202200217650ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include mm_wait_flag_t *mm_wait_flag_create(void) { mm_wait_flag_t *flag = mm_malloc(sizeof(mm_wait_flag_t)); if (flag == NULL) { return NULL; } mm_wait_list_t *waiters = mm_wait_list_create(&flag->value); if (waiters == NULL) { mm_free(flag); return NULL; } flag->waiters = waiters; atomic_init(&flag->value, 0); atomic_init(&flag->link_count, 1); return flag; } static inline void mm_wait_flag_destroy_now(mm_wait_flag_t *flag) { mm_wait_list_free(flag->waiters); mm_free(flag); } static inline void mm_wait_flag_link(mm_wait_flag_t *flag) { atomic_fetch_add(&flag->link_count, 1); } static inline void mm_wait_flag_unlink(mm_wait_flag_t *flag) { if (atomic_fetch_sub(&flag->link_count, 1) == 1) { mm_wait_flag_destroy_now(flag); } } void mm_wait_flag_destroy(mm_wait_flag_t *flag) { mm_wait_flag_unlink(flag); } int mm_wait_flag_wait(mm_wait_flag_t *flag, uint32_t timeout_ms) { uint64_t start_ms = machine_time_ms(); do { uint64_t value = atomic_load(&flag->value); if (value == 1) { return 0; } int rc = mm_wait_list_compare_wait(flag->waiters, NULL, value, timeout_ms); if (rc != 0 && machine_errno() != EAGAIN) { return -1; } } while (machine_time_ms() - start_ms < timeout_ms); mm_errno_set(ETIMEDOUT); return -1; } void mm_wait_flag_set(mm_wait_flag_t *flag) { mm_wait_flag_link(flag); if (atomic_exchange(&flag->value, 1) == 0) { mm_wait_list_notify_all(flag->waiters); } mm_wait_flag_unlink(flag); } MACHINE_API machine_wait_flag_t *machine_wait_flag_create(void) { mm_wait_flag_t *flag; flag = mm_wait_flag_create(); if (flag == NULL) { return NULL; } return mm_cast(machine_wait_flag_t *, flag); } MACHINE_API void machine_wait_flag_destroy(machine_wait_flag_t *flag) { mm_wait_flag_t *flag_; flag_ = mm_cast(mm_wait_flag_t *, flag); mm_wait_flag_destroy(flag_); } MACHINE_API int machine_wait_flag_wait(machine_wait_flag_t *flag, uint32_t timeout_ms) { mm_wait_flag_t *flag_; flag_ = mm_cast(mm_wait_flag_t *, flag); return mm_wait_flag_wait(flag_, timeout_ms); } MACHINE_API void machine_wait_flag_set(machine_wait_flag_t *flag) { mm_wait_flag_t *flag_; flag_ = mm_cast(mm_wait_flag_t *, flag); mm_wait_flag_set(flag_); } odyssey-1.6.0-rc1/sources/machinarium/wait_group.c000066400000000000000000000061271526064202200222110ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include mm_wait_group_t *mm_wait_group_create(void) { mm_wait_group_t *group = mm_malloc(sizeof(mm_wait_group_t)); if (group == NULL) { return NULL; } mm_wait_list_t *waiters = mm_wait_list_create(&group->counter); if (waiters == NULL) { mm_free(group); return NULL; } group->waiters = waiters; atomic_init(&group->counter, 0ULL); atomic_init(&group->link_count, 1); return group; } static inline void mm_wait_group_destroy_now(mm_wait_group_t *group) { mm_wait_list_free(group->waiters); mm_free(group); } static inline void mm_wait_group_link(mm_wait_group_t *group) { atomic_fetch_add(&group->link_count, 1); } static inline void mm_wait_group_unlink(mm_wait_group_t *group) { if (atomic_fetch_sub(&group->link_count, 1) == 1) { mm_wait_group_destroy_now(group); } } void mm_wait_group_destroy(mm_wait_group_t *group) { mm_wait_group_unlink(group); } void mm_wait_group_add(mm_wait_group_t *group) { atomic_fetch_add(&group->counter, 1); mm_wait_group_link(group); } uint64_t mm_wait_group_count(mm_wait_group_t *group) { return atomic_load(&group->counter); } void mm_wait_group_done(mm_wait_group_t *group) { uint64_t old_counter = atomic_fetch_sub(&group->counter, 1); if (old_counter == 0ULL) { /* the counter should not become negative */ abort(); } if (old_counter == 1ULL) { mm_wait_list_notify_all(group->waiters); } mm_wait_group_unlink(group); } int mm_wait_group_wait(mm_wait_group_t *group, uint32_t timeout_ms) { uint64_t start_ms = machine_time_ms(); do { uint64_t old_counter = atomic_load(&group->counter); if (old_counter == 0) { return 0; } int rc = mm_wait_list_compare_wait(group->waiters, NULL, old_counter, timeout_ms); if (rc != 0 && machine_errno() != EAGAIN) { return -1; } } while (machine_time_ms() - start_ms < timeout_ms); mm_errno_set(ETIMEDOUT); return -1; } MACHINE_API machine_wait_group_t *machine_wait_group_create(void) { mm_wait_group_t *group; group = mm_wait_group_create(); if (group == NULL) { return NULL; } return mm_cast(machine_wait_group_t *, group); } MACHINE_API void machine_wait_group_destroy(machine_wait_group_t *group) { mm_wait_group_t *wg; wg = mm_cast(mm_wait_group_t *, group); mm_wait_group_destroy(wg); } MACHINE_API void machine_wait_group_add(machine_wait_group_t *group) { mm_wait_group_t *wg; wg = mm_cast(mm_wait_group_t *, group); mm_wait_group_add(wg); } MACHINE_API uint64_t machine_wait_group_count(machine_wait_group_t *group) { mm_wait_group_t *wg; wg = mm_cast(mm_wait_group_t *, group); return mm_wait_group_count(wg); } MACHINE_API void machine_wait_group_done(machine_wait_group_t *group) { mm_wait_group_t *wg; wg = mm_cast(mm_wait_group_t *, group); mm_wait_group_done(wg); } MACHINE_API int machine_wait_group_wait(machine_wait_group_t *group, uint32_t timeout_ms) { mm_wait_group_t *wg; wg = mm_cast(mm_wait_group_t *, group); return mm_wait_group_wait(wg, timeout_ms); } odyssey-1.6.0-rc1/sources/machinarium/wait_list.c000066400000000000000000000135761526064202200220360ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include /* holding wait_list lock */ static inline void add_sleepy(mm_wait_list_t *wait_list, mm_sleepy_t *sleepy) { mm_list_append(&wait_list->sleepies, &sleepy->link); ++wait_list->sleepy_count; } /* holding wait_list lock */ static inline void release_sleepy(mm_wait_list_t *wait_list, mm_sleepy_t *sleepy) { if (!sleepy->released) { mm_list_unlink(&sleepy->link); --wait_list->sleepy_count; sleepy->released = 1; } } static inline void init_sleepy(mm_sleepy_t *sleepy, void *private, mm_event_t *event) { if (mm_self == NULL || mm_self->scheduler.current == NULL) { abort(); } sleepy->coro_id = mm_self->scheduler.current->id; sleepy->private = private; sleepy->released = 0; mm_list_init(&sleepy->link); if (event) { sleepy->type = SHARED; sleepy->shared_event = event; } else { sleepy->type = EXCLUSIVE; mm_eventmgr_add(&mm_self->event_mgr, &sleepy->event); } } static inline void release_sleepy_with_lock(mm_wait_list_t *wait_list, mm_sleepy_t *sleepy) { mm_sleeplock_lock(&wait_list->lock); release_sleepy(wait_list, sleepy); mm_sleeplock_unlock(&wait_list->lock); } void mm_wait_list_init(mm_wait_list_t *wait_list, atomic_uint_fast64_t *word) { mm_sleeplock_init(&wait_list->lock); mm_list_init(&wait_list->sleepies); wait_list->sleepy_count = 0; wait_list->word = word; } mm_wait_list_t *mm_wait_list_create(atomic_uint_fast64_t *word) { mm_wait_list_t *wait_list = mm_malloc(sizeof(mm_wait_list_t)); if (wait_list == NULL) { return NULL; } mm_wait_list_init(wait_list, word); return wait_list; } void mm_wait_list_free(mm_wait_list_t *wait_list) { mm_wait_list_destroy(wait_list); mm_free(wait_list); } void mm_wait_list_destroy(mm_wait_list_t *wait_list) { (void)wait_list; } static inline int wait_sleepy(mm_wait_list_t *wait_list, mm_sleepy_t *sleepy, uint32_t timeout_ms) { mm_eventmgr_wait(&mm_self->event_mgr, &sleepy->event, timeout_ms); release_sleepy_with_lock(wait_list, sleepy); return sleepy->event.call.status; } int mm_wait_list_wait(mm_wait_list_t *wait_list, void *private, uint32_t timeout_ms) { mm_sleepy_t this; init_sleepy(&this, private, NULL); mm_sleeplock_lock(&wait_list->lock); add_sleepy(wait_list, &this); mm_sleeplock_unlock(&wait_list->lock); int status = wait_sleepy(wait_list, &this, timeout_ms); mm_errno_set(status); if (status != 0) { return -1; } return 0; } int mm_wait_list_compare_wait(mm_wait_list_t *wait_list, void *private, uint64_t expected, uint32_t timeout_ms) { mm_sleeplock_lock(&wait_list->lock); if (atomic_load(wait_list->word) != expected) { mm_sleeplock_unlock(&wait_list->lock); mm_errno_set(EAGAIN); return -1; } mm_sleepy_t this; init_sleepy(&this, private, NULL); add_sleepy(wait_list, &this); mm_sleeplock_unlock(&wait_list->lock); int status = wait_sleepy(wait_list, &this, timeout_ms); mm_errno_set(status); if (status != 0) { return -1; } return 0; } int wakeup_event_from_sleepy(mm_sleepy_t *sleepy) { if (sleepy->type == EXCLUSIVE) { return mm_eventmgr_signal(&sleepy->event); } else { return mm_eventmgr_signal(sleepy->shared_event); } } void *mm_wait_list_notify_cb(mm_wait_list_t *wait_list, mm_wl_private_cb_t cb, void *arg) { mm_sleeplock_lock(&wait_list->lock); if (wait_list->sleepy_count == 0ULL) { mm_sleeplock_unlock(&wait_list->lock); return NULL; } mm_sleepy_t *sleepy; sleepy = mm_list_peek(wait_list->sleepies, mm_sleepy_t); release_sleepy(wait_list, sleepy); int event_mgr_fd; event_mgr_fd = wakeup_event_from_sleepy(sleepy); void *private = sleepy->private; if (cb != NULL) { cb(private, arg); } mm_sleeplock_unlock(&wait_list->lock); if (event_mgr_fd > 0) { mm_eventmgr_wakeup(event_mgr_fd); } return private; } void *mm_wait_list_notify(mm_wait_list_t *wait_list) { return mm_wait_list_notify_cb(wait_list, NULL, NULL); } int mm_wait_list_notify_all(mm_wait_list_t *wait_list) { int signaled = 0; mm_sleeplock_lock(&wait_list->lock); uint64_t count = wait_list->sleepy_count; int *event_mgr_fds = mm_malloc(sizeof(int) * count); if (event_mgr_fds == NULL) { abort(); } if (count > 0) { signaled = 1; } mm_sleepy_t *sleepy; for (uint64_t i = 0; i < count; ++i) { sleepy = mm_list_peek(wait_list->sleepies, mm_sleepy_t); release_sleepy(wait_list, sleepy); event_mgr_fds[i] = wakeup_event_from_sleepy(sleepy); } mm_sleeplock_unlock(&wait_list->lock); for (uint64_t i = 0; i < count; ++i) { if (event_mgr_fds[i] > 0) { mm_eventmgr_wakeup(event_mgr_fds[i]); } } mm_free(event_mgr_fds); return signaled; } int mm_wait_list_waitv(mm_wait_list_t **wait_lists, size_t count, uint32_t timeout_ms) { if (count == 0 || wait_lists == NULL) { mm_errno_set(EINVAL); return -1; } /* prepare shared event for this waiter */ mm_event_t shared_event; mm_sleepy_t *sleepy_list = mm_malloc(count * sizeof(mm_sleepy_t)); if (sleepy_list == NULL) { return -1; } for (size_t i = 0; i < count; ++i) { mm_sleeplock_lock(&wait_lists[i]->lock); } /* register listeners on all provided wait_lists */ for (size_t i = 0; i < count; ++i) { init_sleepy(&sleepy_list[i], NULL, &shared_event); add_sleepy(wait_lists[i], &sleepy_list[i]); } mm_eventmgr_add(&mm_self->event_mgr, &shared_event); for (size_t i = count; i > 0; --i) { mm_sleeplock_unlock(&wait_lists[i - 1]->lock); } /* wait for any listener to signal shared_event */ (void)mm_eventmgr_wait(&mm_self->event_mgr, &shared_event, timeout_ms); for (size_t i = count; i > 0; --i) { release_sleepy_with_lock(wait_lists[i - 1], &sleepy_list[i - 1]); } mm_free(sleepy_list); int status = shared_event.call.status; mm_errno_set(status); if (status != 0) { return -1; } return 0; } odyssey-1.6.0-rc1/sources/machinarium/write.c000066400000000000000000000056151526064202200211640ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include MACHINE_API ssize_t machine_write_raw(mm_io_t *obj, const void *buf, size_t size, size_t *processed) { (void)processed; mm_io_t *io = mm_cast(mm_io_t *, obj); #ifdef MM_BUILD_COMPRESSION /* If streaming compression is enabled then use correspondent compression * write function. */ if (mm_compression_is_active(io)) { return mm_zpq_write(io->zpq_stream, buf, size, processed); } #endif return mm_io_write(io, buf, size); } MACHINE_API ssize_t machine_writev_raw(mm_io_t *io, const struct iovec *iov, int iovcnt) { mm_errno_set(0); int iov_to_write = iovcnt; if (iov_to_write > IOV_MAX) { iov_to_write = IOV_MAX; } ssize_t rc; #ifdef MM_BUILD_COMPRESSION if (mm_compression_is_active(io)) { size_t processed = 0; rc = mm_compression_writev(io, iovec, iov_to_write, &processed); /* processed > 0 in case of error return code, but consumed input */ if (processed > 0) { mm_iov_advance(iov, processed); } } else if (mm_tls_is_active(io)) #else if (mm_tls_is_active(io)) #endif rc = mm_tls_writev(io, iov, iov_to_write); else { rc = mm_socket_writev(io->fd, iov, iov_to_write); } if (rc > 0) { return rc; } int errno_ = errno; mm_errno_set(errno_); if (machine_errno_retryable(errno_)) { return -1; } io->connected = 0; return -1; } MACHINE_API int machine_write_no_free(mm_io_t *destination, machine_msg_t *msg, uint32_t time_ms) { mm_io_t *io = mm_cast(mm_io_t *, destination); mm_errno_set(0); if (!io->attached) { mm_errno_set(ENOTCONN); goto error; } int total = 0; char *src = machine_msg_data(msg); int size = machine_msg_size(msg); /* If compression is on, also check that there is no data left in tx buffer */ while (total != size || mm_compression_write_pending(io)) { /* when using compression, some data may be processed * despite the non-positive return code */ size_t processed = 0; int rc = machine_write_raw(destination, src + total, size - total, &processed); total += processed; if (rc > 0) { total += rc; continue; } /* error or eof */ if (rc == -1) { int errno_ = machine_errno(); if (machine_errno_retryable(errno_)) { rc = mm_io_wait(io, time_ms); if (rc == -1) { goto error; } continue; } } goto error; } return 0; error: return -1; } /* writes msg to io object. * Frees memory after use in current implementation * */ MACHINE_API int machine_write(mm_io_t *destination, machine_msg_t *msg, uint32_t time_ms) { int rc = machine_write_no_free(destination, msg, time_ms); machine_msg_free(msg); return rc; } odyssey-1.6.0-rc1/sources/machinarium/zpq_stream.c000066400000000000000000000356701526064202200222230ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include /* * Functions implementing streaming compression algorithm */ typedef struct { /* * Returns letter identifying compression algorithm. */ char (*name)(void); /* * Create compression stream with using rx/tx function for fetching/sending * compressed data. tx_func: function for writing compressed data in * underlying stream rx_func: function for receiving compressed data from * underlying stream arg: context passed to the function rx_data: received * data (compressed data already fetched from input stream) rx_data_size: * size of data fetched from input stream */ mm_zpq_stream_t *(*create)(mm_zpq_tx_func tx_func, mm_zpq_rx_func rx_func, void *arg, char *rx_data, size_t rx_data_size); /* * Read up to "size" raw (decompressed) bytes. * Returns number of decompressed bytes or error code. * Error code is either ZPQ_DECOMPRESS_ERROR either error code returned by * the rx function. */ ssize_t (*read)(mm_zpq_stream_t *zs, void *buf, size_t size); /* * Write up to "size" raw (decompressed) bytes. * Returns number of written raw bytes or error code returned by tx * function. In the last case amount of written raw bytes is stored in * *processed. */ ssize_t (*write)(mm_zpq_stream_t *zs, void const *buf, size_t size, size_t *processed); /* * Free stream created by create function. */ void (*free)(mm_zpq_stream_t *zs); /* * Get error message. */ char const *(*error)(mm_zpq_stream_t *zs); /* * Returns estimated amount of data left in internal tx decompression * buffer. */ size_t (*buffered_tx)(mm_zpq_stream_t *zs); /* * Returns estimated amount of data left in internal rx compression * buffer. */ size_t (*buffered_rx)(mm_zpq_stream_t *zs); /* * Returns 1 if there is deferred rx_func call operation. * Otherwise returns 0. */ _Bool (*deferred_rx)(mm_zpq_stream_t *zs); } zpq_algorithm_t; struct mm_zpq_stream { zpq_algorithm_t const *algorithm; }; #ifdef MM_BUILD_COMPRESSION #ifdef MM_HAVE_ZSTD #include #include #define MM_ZSTD_BUFFER_SIZE (8 * 1024) #define MM_ZSTD_COMPRESSION_LEVEL 1 typedef struct zstd_stream { mm_zpq_stream_t common; ZSTD_CStream *tx_stream; ZSTD_DStream *rx_stream; ZSTD_outBuffer tx; ZSTD_inBuffer rx; size_t tx_not_flushed; /* Amount of data in internal zstd buffer */ size_t tx_buffered; /* Data consumed by zstd_write but not yet sent */ size_t rx_buffered; /* Data which is needed for ztd_read */ /* Flag that the last call of zstd_read did not call the rx_func */ _Bool deferred_rx_call; mm_zpq_tx_func tx_func; mm_zpq_rx_func rx_func; void *arg; char const *rx_error; /* Decompress error message */ size_t tx_total; size_t tx_total_raw; size_t rx_total; size_t rx_total_raw; char tx_buf[MM_ZSTD_BUFFER_SIZE]; char rx_buf[MM_ZSTD_BUFFER_SIZE]; } zstd_stream_t; static mm_zpq_stream_t *zstd_create(mm_zpq_tx_func tx_func, mm_zpq_rx_func rx_func, void *arg, char *rx_data, size_t rx_data_size) { zstd_stream_t *zs = (zstd_stream_t *)mm_malloc(sizeof(zstd_stream_t)); zs->tx_stream = ZSTD_createCStream(); ZSTD_initCStream(zs->tx_stream, MM_ZSTD_COMPRESSION_LEVEL); zs->rx_stream = ZSTD_createDStream(); ZSTD_initDStream(zs->rx_stream); zs->tx.dst = zs->tx_buf; zs->tx.pos = 0; zs->tx.size = MM_ZSTD_BUFFER_SIZE; zs->rx.src = zs->rx_buf; zs->rx.pos = 0; zs->rx.size = 0; zs->rx_func = rx_func; zs->tx_func = tx_func; zs->tx_buffered = 0; zs->rx_buffered = 0; zs->tx_not_flushed = 0; zs->rx_error = NULL; zs->arg = arg; zs->tx_total = zs->tx_total_raw = 0; zs->rx_total = zs->rx_total_raw = 0; zs->rx.size = rx_data_size; zs->deferred_rx_call = 0; assert(rx_data_size < MM_ZSTD_BUFFER_SIZE); memcpy(zs->rx_buf, rx_data, rx_data_size); return (mm_zpq_stream_t *)zs; } static ssize_t zstd_read(mm_zpq_stream_t *zstream, void *buf, size_t size) { zstd_stream_t *zs = (zstd_stream_t *)zstream; ssize_t rc; ZSTD_outBuffer out; out.dst = buf; out.pos = 0; out.size = size; for (;;) { /* store the incomplete rx attempt flag */ zs->deferred_rx_call = 1; if (zs->rx.pos != zs->rx.size || zs->rx_buffered == 0) { rc = ZSTD_decompressStream(zs->rx_stream, &out, &zs->rx); if (ZSTD_isError(rc)) { zs->rx_error = ZSTD_getErrorName(rc); return MM_ZPQ_DECOMPRESS_ERROR; } /* Return result if we fill requested amount of bytes or read * operation was performed */ if (out.pos != 0) { zs->rx_total_raw += out.pos; zs->rx_buffered = 0; return out.pos; } zs->rx_buffered = rc; if (zs->rx.pos == zs->rx.size) { zs->rx.pos = zs->rx.size = 0; /* Reset rx buffer */ } } rc = zs->rx_func(zs->arg, (char *)zs->rx.src + zs->rx.size, MM_ZSTD_BUFFER_SIZE - zs->rx.size); /* if we've made a call to rx function, reset the deferred rx flag */ zs->deferred_rx_call = 0; if (rc > 0) /* read fetches some data */ { zs->rx.size += rc; zs->rx_total += rc; } else /* read failed */ { zs->rx_total_raw += out.pos; return rc; } } } static ssize_t zstd_write(mm_zpq_stream_t *zstream, void const *buf, size_t size, size_t *processed) { zstd_stream_t *zs = (zstd_stream_t *)zstream; ssize_t rc; ZSTD_inBuffer in_buf; in_buf.src = buf; in_buf.pos = 0; in_buf.size = size; do { if (zs->tx.pos == 0) /* Compress buffer is empty */ { zs->tx.dst = zs->tx_buf; /* Reset pointer to the beginning of buffer */ if (in_buf.pos < size) { /* Has something to compress in input buffer */ ZSTD_compressStream(zs->tx_stream, &zs->tx, &in_buf); } if (in_buf.pos == size) /* All data is compressed: flushed internal zstd buffer */ { zs->tx_not_flushed = ZSTD_flushStream( zs->tx_stream, &zs->tx); } } rc = zs->tx_func(zs->arg, zs->tx.dst, zs->tx.pos); if (rc > 0) { zs->tx.pos -= rc; zs->tx.dst = (char *)zs->tx.dst + rc; zs->tx_total += rc; } else { *processed = in_buf.pos; zs->tx_buffered = zs->tx.pos; zs->tx_total_raw += in_buf.pos; return rc; } /* repeat sending while there is some data in input or internal zstd * buffer */ } while (in_buf.pos < size || zs->tx_not_flushed); zs->tx_total_raw += in_buf.pos; zs->tx_buffered = zs->tx.pos; return in_buf.pos; } static void zstd_free(mm_zpq_stream_t *zstream) { zstd_stream_t *zs = (zstd_stream_t *)zstream; if (zs != NULL) { ZSTD_freeCStream(zs->tx_stream); ZSTD_freeDStream(zs->rx_stream); mm_free(zs); } } static char const *zstd_error(mm_zpq_stream_t *zstream) { zstd_stream_t *zs = (zstd_stream_t *)zstream; return zs->rx_error; } static size_t zstd_buffered_tx(mm_zpq_stream_t *zstream) { zstd_stream_t *zs = (zstd_stream_t *)zstream; return zs != NULL ? zs->tx_buffered + zs->tx_not_flushed : 0; } static size_t zstd_buffered_rx(mm_zpq_stream_t *zstream) { zstd_stream_t *zs = (zstd_stream_t *)zstream; return zs != NULL ? zs->rx.size - zs->rx.pos : 0; } static _Bool zstd_deferred_rx(mm_zpq_stream_t *zstream) { zstd_stream_t *zs = (zstd_stream_t *)zstream; return zs != NULL ? zs->deferred_rx_call : 0; } static char zstd_name(void) { return 'f'; } #endif #ifdef MM_HAVE_ZLIB #include #include #define MM_ZLIB_BUFFER_SIZE \ 8192 /* We have to flush stream after each protocol command \ * and command is mostly limited by record length, \ * which in turn usually less than page size (except TOAST) \ */ #define MM_ZLIB_COMPRESSION_LEVEL \ 1 /* Experiments shows that default (fastest) compression level \ * provides the best size/speed ratio. It is significantly \ * (times) faster than more expensive levels and differences in \ * compression ratio is not so large \ */ typedef struct zlib_stream { mm_zpq_stream_t common; z_stream tx; z_stream rx; mm_zpq_tx_func tx_func; mm_zpq_rx_func rx_func; void *arg; unsigned tx_deflate_pending; /* Flag that the last call of zlib_read did not call the rx_func */ _Bool deferred_rx_call; size_t tx_buffered; Bytef tx_buf[MM_ZLIB_BUFFER_SIZE]; Bytef rx_buf[MM_ZLIB_BUFFER_SIZE]; } zlib_stream_t; static mm_zpq_stream_t *zlib_create(mm_zpq_tx_func tx_func, mm_zpq_rx_func rx_func, void *arg, char *rx_data, size_t rx_data_size) { int rc; zlib_stream_t *zs = (zlib_stream_t *)mm_malloc(sizeof(zlib_stream_t)); memset(&zs->tx, 0, sizeof(zs->tx)); zs->tx.next_out = zs->tx_buf; zs->tx.avail_out = MM_ZLIB_BUFFER_SIZE; zs->tx_buffered = 0; rc = deflateInit(&zs->tx, MM_ZLIB_COMPRESSION_LEVEL); if (rc != Z_OK) { mm_free(zs); return NULL; } assert(zs->tx.next_out == zs->tx_buf && zs->tx.avail_out == MM_ZLIB_BUFFER_SIZE); memset(&zs->rx, 0, sizeof(zs->tx)); zs->rx.next_in = zs->rx_buf; zs->rx.avail_in = MM_ZLIB_BUFFER_SIZE; zs->tx_deflate_pending = 0; zs->deferred_rx_call = 0; rc = inflateInit(&zs->rx); if (rc != Z_OK) { mm_free(zs); return NULL; } assert(zs->rx.next_in == zs->rx_buf && zs->rx.avail_in == MM_ZLIB_BUFFER_SIZE); zs->rx.avail_in = rx_data_size; assert(rx_data_size < MM_ZLIB_BUFFER_SIZE); memcpy(zs->rx_buf, rx_data, rx_data_size); zs->rx_func = rx_func; zs->tx_func = tx_func; zs->arg = arg; return (mm_zpq_stream_t *)zs; } static ssize_t zlib_read(mm_zpq_stream_t *zstream, void *buf, size_t size) { zlib_stream_t *zs = (zlib_stream_t *)zstream; int rc; zs->rx.next_out = (Bytef *)buf; zs->rx.avail_out = size; for (;;) { /* store the incomplete rx attempt flag */ zs->deferred_rx_call = 1; if (zs->rx.avail_in != 0) /* If there is some data in receiver buffer, then decompress it */ { rc = inflate(&zs->rx, Z_SYNC_FLUSH); if (rc != Z_OK && rc != Z_BUF_ERROR) { return MM_ZPQ_DECOMPRESS_ERROR; } if (zs->rx.avail_out != size) { return size - zs->rx.avail_out; } if (zs->rx.avail_in == 0) { zs->rx.next_in = zs->rx_buf; } } else { zs->rx.next_in = zs->rx_buf; } rc = zs->rx_func(zs->arg, zs->rx.next_in + zs->rx.avail_in, zs->rx_buf + MM_ZLIB_BUFFER_SIZE - zs->rx.next_in - zs->rx.avail_in); /* if we've made a call to rx function, reset the deferred rx flag */ zs->deferred_rx_call = 0; if (rc > 0) { zs->rx.avail_in += rc; } else { return rc; } } } static ssize_t zlib_write(mm_zpq_stream_t *zstream, void const *buf, size_t size, size_t *processed) { zlib_stream_t *zs = (zlib_stream_t *)zstream; int rc; zs->tx.next_in = (Bytef *)buf; zs->tx.avail_in = size; do { if (zs->tx.avail_out == MM_ZLIB_BUFFER_SIZE) /* Compress buffer is empty */ { zs->tx.next_out = zs->tx_buf; /* Reset pointer to the beginning of buffer */ if (zs->tx.avail_in != 0 || (zs->tx_deflate_pending > 0)) /* Has something in input or deflate buffer */ { rc = deflate(&zs->tx, Z_SYNC_FLUSH); assert(rc == Z_OK); deflatePending( &zs->tx, &zs->tx_deflate_pending, Z_NULL); /* check if any data left in deflate buffer */ zs->tx.next_out = zs->tx_buf; /* Reset pointer to the beginning of buffer */ } } rc = zs->tx_func(zs->arg, zs->tx.next_out, MM_ZLIB_BUFFER_SIZE - zs->tx.avail_out); if (rc > 0) { zs->tx.next_out += rc; zs->tx.avail_out += rc; } else { *processed = size - zs->tx.avail_in; zs->tx_buffered = MM_ZLIB_BUFFER_SIZE - zs->tx.avail_out; return rc; } /* repeat sending while there is some data in input or deflate buffer */ } while (zs->tx.avail_in != 0 || zs->tx_deflate_pending > 0); zs->tx_buffered = MM_ZLIB_BUFFER_SIZE - zs->tx.avail_out; return size - zs->tx.avail_in; } static void zlib_free(mm_zpq_stream_t *zstream) { zlib_stream_t *zs = (zlib_stream_t *)zstream; if (zs != NULL) { inflateEnd(&zs->rx); deflateEnd(&zs->tx); mm_free(zs); } } static char const *zlib_error(mm_zpq_stream_t *zstream) { zlib_stream_t *zs = (zlib_stream_t *)zstream; return zs->rx.msg; } static size_t zlib_buffered_tx(mm_zpq_stream_t *zstream) { zlib_stream_t *zs = (zlib_stream_t *)zstream; return zs != NULL ? zs->tx_buffered + zs->tx_deflate_pending : 0; } static size_t zlib_buffered_rx(mm_zpq_stream_t *zstream) { zlib_stream_t *zs = (zlib_stream_t *)zstream; return zs != NULL ? zs->rx.avail_in : 0; } static _Bool zlib_deferred_rx(mm_zpq_stream_t *zstream) { zlib_stream_t *zs = (zlib_stream_t *)zstream; return zs != NULL ? zs->deferred_rx_call : 0; } static char zlib_name(void) { return 'z'; } #endif #endif /* * Array with all supported compression algorithms. */ static zpq_algorithm_t const zpq_algorithms[] = { #ifdef MM_BUILD_COMPRESSION #ifdef MM_HAVE_ZSTD { zstd_name, zstd_create, zstd_read, zstd_write, zstd_free, zstd_error, zstd_buffered_tx, zstd_buffered_rx, zstd_deferred_rx }, #endif #ifdef MM_HAVE_ZLIB { zlib_name, zlib_create, zlib_read, zlib_write, zlib_free, zlib_error, zlib_buffered_tx, zlib_buffered_rx, zlib_deferred_rx }, #endif #endif { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; /* * Index of used compression algorithm in zpq_algorithms array. */ mm_zpq_stream_t *zpq_create(int algorithm_impl, mm_zpq_tx_func tx_func, mm_zpq_rx_func rx_func, void *arg, char *rx_data, size_t rx_data_size) { mm_zpq_stream_t *stream = zpq_algorithms[algorithm_impl].create( tx_func, rx_func, arg, rx_data, rx_data_size); if (stream) { stream->algorithm = &zpq_algorithms[algorithm_impl]; } return stream; } ssize_t mm_zpq_read(mm_zpq_stream_t *zs, void *buf, size_t size) { return zs->algorithm->read(zs, buf, size); } ssize_t mm_zpq_write(mm_zpq_stream_t *zs, void const *buf, size_t size, size_t *processed) { return zs->algorithm->write(zs, buf, size, processed); } void mm_zpq_free(mm_zpq_stream_t *zs) { if (zs) { zs->algorithm->free(zs); } } char const *mm_zpq_error(mm_zpq_stream_t *zs) { return zs->algorithm->error(zs); } size_t mm_zpq_buffered_rx(mm_zpq_stream_t *zs) { return zs ? zs->algorithm->buffered_rx(zs) : 0; } size_t mm_zpq_buffered_tx(mm_zpq_stream_t *zs) { return zs ? zs->algorithm->buffered_tx(zs) : 0; } _Bool mm_zpq_deferred_rx(mm_zpq_stream_t *zs) { return zs ? zs->algorithm->deferred_rx(zs) : 0; } /* * Get list of the supported algorithms. * Each algorithm is identified by one letter: 'f' - Facebook zstd, 'z' - zlib. * Algorithm identifies are appended to the provided buffer and terminated by * '\0'. */ void mm_zpq_get_supported_algorithms(char *algorithms) { int i; for (i = 0; zpq_algorithms[i].name != NULL; i++) { assert(i < MM_ZPQ_MAX_ALGORITHMS); algorithms[i] = zpq_algorithms[i].name(); } assert(i < MM_ZPQ_MAX_ALGORITHMS); algorithms[i] = '\0'; } /* * Choose current algorithm implementation. * Returns implementation number or -1 if algorithm with such name is not found */ int mm_zpq_get_algorithm_impl(char name) { int i; if (name != MM_ZPQ_NO_COMPRESSION) { for (i = 0; zpq_algorithms[i].name != NULL; i++) { if (zpq_algorithms[i].name() == name) { return i; } } } return -1; } odyssey-1.6.0-rc1/sources/main.c000066400000000000000000000067031526064202200164600ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #ifdef __darwin__ #include #endif #ifdef USE_TCMALLOC_PROFILE #include #include static inline void init_tcmalloc_profile(void) { const char *heapprofile_path = getenv("HEAPPROFILE"); if (heapprofile_path == NULL) { return; } fprintf(stderr, "Starting heap profiler: %s\n", heapprofile_path); HeapProfilerStart(heapprofile_path); if (!IsHeapProfilerRunning()) { abort(); } } #else static inline void init_tcmalloc_profile(void) { } #endif /* USE_TCMALLOC_PROFILE */ extern char **environ; static char **previous_environ = NULL; /* * same preparation as in PostgreSQL (src/backend/utils/misc/ps_status.c, PS_USE_CLOBBER_ARGV). */ static char **prepare_proctitle_buffer(od_instance_t *instance, int argc, char **argv) { char *end_of_area = NULL; int i; for (i = 0; i < argc; i++) { if (i == 0 || end_of_area + 1 == argv[i]) { end_of_area = argv[i] + strlen(argv[i]); } } if (end_of_area == NULL) { instance->orig_argv_ptr = NULL; instance->orig_argv_ptr_len = 0; return argv; } for (i = 0; environ[i] != NULL; i++) { if (end_of_area + 1 == environ[i]) { end_of_area = environ[i] + strlen(environ[i]); } } instance->orig_argv_ptr = argv[0]; instance->orig_argv_ptr_len = end_of_area - argv[0]; char **new_environ = (char **)malloc((i + 1) * sizeof(char *)); if (new_environ == NULL) { instance->orig_argv_ptr = NULL; instance->orig_argv_ptr_len = 0; return argv; } for (i = 0; environ[i] != NULL; i++) { new_environ[i] = strdup(environ[i]); if (new_environ[i] == NULL) { new_environ[i] = NULL; while (--i >= 0) { free(new_environ[i]); } free(new_environ); instance->orig_argv_ptr = NULL; instance->orig_argv_ptr_len = 0; return argv; } } new_environ[i] = NULL; previous_environ = environ; environ = new_environ; char **new_argv = (char **)malloc((argc + 1) * sizeof(char *)); if (new_argv == NULL) { return argv; } for (i = 0; i < argc; i++) { new_argv[i] = strdup(argv[i]); if (new_argv[i] == NULL) { new_argv[i] = NULL; while (--i >= 0) { free(new_argv[i]); } free(new_argv); return argv; } } new_argv[argc] = NULL; #ifdef __darwin__ /* * macOS keeps a static copy of the argv pointer; update it */ *_NSGetArgv() = new_argv; #endif /* * make the remaining original argv slots (beyond argv[0]) point at * the end of the clobber area (a NUL), so that ps shows a single * string instead of concatenating stale argv entries */ for (i = 1; i < argc; i++) { argv[i] = end_of_area; } return new_argv; } int main(int argc, char *argv[], char *envp[]) { init_tcmalloc_profile(); od_instance_t *odyssey = od_instance_create(); char **argv_for_parse = prepare_proctitle_buffer(odyssey, argc, argv); (void)envp; int rc = od_instance_main(odyssey, argc, argv_for_parse, environ); if (rc == -1) { rc = EXIT_FAILURE; } else { rc = EXIT_SUCCESS; } /* clear the allocation to make the ASAN be silent */ if (argv != argv_for_parse) { for (char **p = argv_for_parse; *p != NULL; ++p) { free(*p); } free(argv_for_parse); #ifdef __darwin__ *_NSGetArgv() = argv; #endif } if (previous_environ != NULL) { char **allocated = environ; environ = previous_environ; for (char **p = allocated; *p != NULL; ++p) { free(*p); } free(allocated); } return rc; } odyssey-1.6.0-rc1/sources/mdb_iamproxy.c000066400000000000000000000200251526064202200202170ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include /*CONNECTION CALLBACK TYPES*/ #define MDB_IAMPROXY_CONN_ERROR -1 #define MDB_IAMPROXY_CONN_TIMEOUT -1 #define MDB_IAMPROXY_CONN_ACCEPTED 0 #define MDB_IAMPROXY_CONN_DENIED -1 #define MDB_IAMPROXY_RES_ERROR -1 #define MDB_IAMPROXY_RES_OK 0 /*AUTHENTICATION TIMEOUT LIMIT*/ #define MDB_IAMPROXY_DEFAULT_HEADER_SIZE 8 #define MDB_IAMPROXY_DEFAULT_CNT_CONNECTIONS 1 #define MDB_IAMPROXY_MAX_MSG_BODY_SIZE 1048576 /* 1 Mb */ #define MDB_IAMPROXY_DEFAULT_CONNECTION_TIMEOUT 1000 #define MDB_IAMPROXY_DEFAULT_RECEIVING_HEADER_TIMEOUT 4000 #define MDB_IAMPROXY_DEFAULT_RECEIVING_BODY_TIMEOUT 1000 #define MDB_IAMPROXY_DEFAULT_SENDING_TIMEOUT 1000 /*PAM SOCKET FILE*/ #define MDB_IAMPROXY_DEFAULT_SOCKET_FILE \ "/var/run/iam-auth-proxy/iam-auth-proxy.sock" /* PAM SOCKET FILE place */ static void put_header(char dst[], uint64_t src) { for (int i = 0; i < MDB_IAMPROXY_DEFAULT_HEADER_SIZE; ++i) { dst[i] = (src & 0xFF); src >>= CHAR_BIT; } } static void fetch_header(uint64_t *dst, char src[]) { for (int i = 0; i < MDB_IAMPROXY_DEFAULT_HEADER_SIZE; ++i) { (*dst) |= (((uint64_t)src[i]) << (i * CHAR_BIT)); } } machine_msg_t *mdb_iamproxy_io_read(mm_io_t *io) { machine_msg_t *header; machine_msg_t *msg; uint64_t body_size = 0; /* RECEIVE HEADER */ header = machine_read(io, MDB_IAMPROXY_DEFAULT_HEADER_SIZE, MDB_IAMPROXY_DEFAULT_RECEIVING_HEADER_TIMEOUT); if (header == NULL) { return NULL; } fetch_header(&body_size, (char *)machine_msg_data(header)); machine_msg_free(header); if (body_size > MDB_IAMPROXY_MAX_MSG_BODY_SIZE) { return NULL; } msg = machine_read(io, body_size, MDB_IAMPROXY_DEFAULT_RECEIVING_BODY_TIMEOUT); if (msg == NULL) { return NULL; } return msg; } int mdb_iamproxy_io_write(mm_io_t *io, machine_msg_t *msg) { /*GET COMMON MSG INFO AND ALLOCATE BUFFER*/ int32_t send_result = MDB_IAMPROXY_RES_OK; uint64_t body_size = machine_msg_size( msg); /* stores size of message (add one byte for 'c\0') */ /* PREPARE HEADER BUFFER */ machine_msg_t *header = machine_msg_create(MDB_IAMPROXY_DEFAULT_HEADER_SIZE); if (header == NULL) { send_result = MDB_IAMPROXY_RES_ERROR; goto free_msg; } put_header((char *)machine_msg_data(header), body_size); /*SEND HEADER TO SOCKET*/ if (machine_write(io, header, MDB_IAMPROXY_DEFAULT_SENDING_TIMEOUT) < 0) { send_result = MDB_IAMPROXY_RES_ERROR; goto free_msg; } /*SEND MSG TO SOCKET*/ if (machine_write(io, msg, MDB_IAMPROXY_DEFAULT_SENDING_TIMEOUT) < 0) { send_result = MDB_IAMPROXY_RES_ERROR; goto free_end; } goto free_end; free_msg: machine_msg_free( msg); /* guarantee that the memory will be freed when the function is completed */ free_end: return send_result; } int mdb_iamproxy_authenticate_user( char *username, char *token, /* remove const because machine_msg_write use as buf - non constant values (but do nothing ith them....) */ od_instance_t *instance, od_client_t *client) { int32_t authentication_result = MDB_IAMPROXY_CONN_DENIED; /* stores authenticate status for user (default value: CONN_DENIED) */ int32_t correct_sending = MDB_IAMPROXY_CONN_ACCEPTED; /* stores status of sending data to iam-auth-proxy */ char *auth_status_char; machine_msg_t *msg_username = NULL, *msg_token = NULL, *auth_status = NULL, *external_user = NULL; /*SOCKET SETUP*/ struct sockaddr *saddr; struct sockaddr_un exchange_socket; /* socket for interprocceses connection */ memset(&exchange_socket, 0, sizeof(exchange_socket)); exchange_socket.sun_family = AF_UNIX; saddr = (struct sockaddr *)&exchange_socket; /* if socket path set use config value, if it's NULL use default */ if (client->rule->mdb_iamproxy_socket_path == NULL) { od_snprintf(exchange_socket.sun_path, sizeof(exchange_socket.sun_path), "%s", MDB_IAMPROXY_DEFAULT_SOCKET_FILE); } else { od_snprintf(exchange_socket.sun_path, sizeof(exchange_socket.sun_path), "%s", client->rule->mdb_iamproxy_socket_path); } /*SETUP IO*/ mm_io_t *io; io = mm_io_create(); if (io == NULL) { authentication_result = MDB_IAMPROXY_CONN_ERROR; goto free_end; } /*CONNECT TO SOCKET*/ int rc = mm_io_connect(io, saddr, MDB_IAMPROXY_DEFAULT_CONNECTION_TIMEOUT); if (rc == NOT_OK_RESPONSE) { od_error(&instance->logger, "auth", client, NULL, "failed to connect to %s", exchange_socket.sun_path); authentication_result = MDB_IAMPROXY_CONN_ERROR; goto free_end; } /*COMMUNICATE WITH SOCKET*/ msg_username = machine_msg_create(0); if (msg_username == NULL) { od_error(&instance->logger, "auth", client, NULL, "failed to allocate msg_username"); authentication_result = MDB_IAMPROXY_CONN_ERROR; goto free_io; } if (machine_msg_write(msg_username, username, strlen(username) + 1) < 0) { od_error(&instance->logger, "auth", client, NULL, "failed to send username to msg_username"); authentication_result = MDB_IAMPROXY_CONN_ERROR; machine_msg_free(msg_username); goto free_io; } msg_token = machine_msg_create(0); if (msg_token == NULL) { od_error(&instance->logger, "auth", client, NULL, "failed to allocate msg_token"); authentication_result = MDB_IAMPROXY_CONN_ERROR; goto free_io; } if (machine_msg_write(msg_token, token, strlen(token) + 1) < 0) { od_error(&instance->logger, "auth", client, NULL, "failed to write token to msg_token"); authentication_result = MDB_IAMPROXY_CONN_ERROR; machine_msg_free(msg_token); goto free_io; } correct_sending = mdb_iamproxy_io_write( io, msg_username); /* send USERNAME to socket */ if (correct_sending != MDB_IAMPROXY_RES_OK) { /* error during sending data to socket */ od_error(&instance->logger, "auth", client, NULL, "failed to send username to iam-auth-proxy"); authentication_result = correct_sending; /* have guarantee that msg_username have been freed, but need to free msg_token */ machine_msg_free(msg_token); goto free_io; } correct_sending = mdb_iamproxy_io_write(io, msg_token); /* send TOKEN to socket */ if (correct_sending != MDB_IAMPROXY_RES_OK) { /* error during sending data to socket */ od_error(&instance->logger, "auth", client, NULL, "failed to send token to iam-auth-proxy"); authentication_result = MDB_IAMPROXY_CONN_ERROR; /* have guarantee that msg_token have been already freed */ goto free_io; } /*COMMUNUCATE WITH SOCKET*/ auth_status = mdb_iamproxy_io_read(io); /* receive auth_status from socket */ if (auth_status == NULL || machine_msg_size(auth_status) < 1) { /* receiving is not completed successfully */ od_error(&instance->logger, "auth", client, NULL, "failed to receive auth_status from iam-auth-proxy"); authentication_result = MDB_IAMPROXY_CONN_ERROR; goto free_io; } auth_status_char = (char *)machine_msg_data(auth_status); if ((unsigned)auth_status_char[0]) { authentication_result = MDB_IAMPROXY_CONN_ACCEPTED; } else { authentication_result = MDB_IAMPROXY_CONN_DENIED; } external_user = mdb_iamproxy_io_read(io); /* receive subject_id from socket */ if (external_user == NULL) { od_error(&instance->logger, "auth", client, NULL, "failed to receive external_user from iam-auth-proxy"); authentication_result = MDB_IAMPROXY_CONN_ERROR; goto free_auth_status; } client->external_id = od_malloc(machine_msg_size(external_user)); memcpy(client->external_id, (char *)machine_msg_data(external_user), machine_msg_size(external_user)); od_log(&instance->logger, "auth", client, NULL, "user '%s.%s', with client_id: %s%.*s was authenticated by iam with subject_id: %s", client->startup.database.value, client->startup.user.value, client->id.id_prefix, OD_ID_LEN, client->id.id, client->external_id); /*FREE RESOURCES*/ machine_msg_free(external_user); free_auth_status: machine_msg_free(auth_status); free_io: mm_io_close(io); mm_io_free(io); free_end: /*RETURN RESULT*/ return authentication_result; } odyssey-1.6.0-rc1/sources/misc.c000066400000000000000000000052121526064202200164610ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include int pg_strcasecmp(const char *s1, const char *s2) { for (;;) { unsigned char ch1 = (unsigned char)*s1++; unsigned char ch2 = (unsigned char)*s2++; if (ch1 != ch2) { if (ch1 >= 'A' && ch1 <= 'Z') { ch1 += 'a' - 'A'; } else if (IS_HIGHBIT_SET(ch1) && isupper(ch1)) { ch1 = tolower(ch1); } if (ch2 >= 'A' && ch2 <= 'Z') { ch2 += 'a' - 'A'; } else if (IS_HIGHBIT_SET(ch2) && isupper(ch2)) { ch2 = tolower(ch2); } if (ch1 != ch2) { return (int)ch1 - (int)ch2; } } if (ch1 == 0) { break; } } return 0; } int pg_strncasecmp(const char *s1, const char *s2, size_t n) { while (n-- > 0) { unsigned char ch1 = (unsigned char)*s1++; unsigned char ch2 = (unsigned char)*s2++; if (ch1 != ch2) { if (ch1 >= 'A' && ch1 <= 'Z') { ch1 += 'a' - 'A'; } else if (IS_HIGHBIT_SET(ch1) && isupper(ch1)) { ch1 = tolower(ch1); } if (ch2 >= 'A' && ch2 <= 'Z') { ch2 += 'a' - 'A'; } else if (IS_HIGHBIT_SET(ch2) && isupper(ch2)) { ch2 = tolower(ch2); } if (ch1 != ch2) { return (int)ch1 - (int)ch2; } } if (ch1 == 0) { break; } } return 0; } bool parse_bool_with_len(const char *value, size_t len, bool *result) { switch (*value) { case 't': case 'T': if (pg_strncasecmp(value, "true", len) == 0) { if (result) { *result = true; } return true; } break; case 'f': case 'F': if (pg_strncasecmp(value, "false", len) == 0) { if (result) { *result = false; } return true; } break; case 'y': case 'Y': if (pg_strncasecmp(value, "yes", len) == 0) { if (result) { *result = true; } return true; } break; case 'n': case 'N': if (pg_strncasecmp(value, "no", len) == 0) { if (result) { *result = false; } return true; } break; case 'o': case 'O': /* 'o' is not unique enough */ if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) { if (result) { *result = true; } return true; } else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) { if (result) { *result = false; } return true; } break; case '1': if (len == 1) { if (result) { *result = true; } return true; } break; case '0': if (len == 1) { if (result) { *result = false; } return true; } break; default: break; } if (result) { *result = false; /* suppress compiler warning */ } return false; } bool parse_bool(const char *value, bool *result) { return parse_bool_with_len(value, strlen(value), result); } odyssey-1.6.0-rc1/sources/module.c000066400000000000000000000104011526064202200170070ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include void od_modules_init(od_module_t *module) { od_list_init(&module->link); } od_module_t *od_modules_find(od_module_t *modules, char *target_module_path) { od_list_t *i; od_list_foreach (&modules->link, i) { od_module_t *m; m = od_container_of(i, od_module_t, link); if (strcmp(m->path, target_module_path) == 0) { return m; } } return NULL; } int od_target_module_add(od_logger_t *logger, od_module_t *modules, char *target_module_path) { od_list_t *i; od_list_foreach (&modules->link, i) { od_module_t *m; m = od_container_of(i, od_module_t, link); if (strcmp(m->path, target_module_path) == 0) { goto module_exists; } } void *handle; od_module_t *module_ptr; char *err; handle = od_dlopen(target_module_path); if (!handle) { if (logger) { od_log(logger, "load_module", NULL, NULL, "errors while opening %s module: %s", target_module_path, dlerror()); } else { printf("errors while opening %s module: %s", target_module_path, dlerror()); } goto error; } module_ptr = od_load_module(handle); if ((err = dlerror()) != NULL) { goto error_close_handle; } if (strlen(module_ptr->path) + strlen(target_module_path) + 1 > sizeof(module_ptr->path)) { goto error_close_handle; } module_ptr->handle = handle; od_list_init(&module_ptr->link); od_list_append(&modules->link, &module_ptr->link); strcat(module_ptr->path, target_module_path); if (module_ptr->module_init_cb) { return module_ptr->module_init_cb(logger); } return OD_MODULE_CB_OK_RETCODE; module_exists: if (logger == NULL) { /* most probably its logger is not ready yet */ } else { od_log(logger, "od_load_module", NULL, NULL, "od_load_module: skip load module %s: was already loaded!", target_module_path); } return OD_MODULE_CB_FAIL_RETCODE; error_close_handle: od_dlclose(handle); error: err = od_dlerror(); if (logger) { od_log(logger, "od_load_module", NULL, NULL, "od_load_module: failed to load module %s", err); } return OD_MODULE_CB_FAIL_RETCODE; } static inline void od_module_free(od_module_t *module) { od_list_unlink(&module->link); } int od_target_module_unload(od_logger_t *logger, od_module_t *modules, char *target_module) { char *err; od_list_t *i; od_list_foreach (&modules->link, i) { od_module_t *m; m = od_container_of(i, od_module_t, link); if (strcmp(m->path, target_module) == 0) { int rc; rc = m->unload_cb(); if (rc != OD_MODULE_CB_OK_RETCODE) { return -1; } void *h = m->handle; m->handle = NULL; od_module_free(m); /* because we cannot access handle after calling dlclose */ if (od_dlclose(h)) { goto error; } return OD_MODULE_CB_OK_RETCODE; } } od_log(logger, "od target module unload failed", NULL, NULL, "od_module_unload: failed to find specified module to unload %s", target_module); return OD_MODULE_CB_FAIL_RETCODE; error: err = od_dlerror(); od_log(logger, "od unload module error", NULL, NULL, "od_module_unload: %s", err); return OD_MODULE_CB_FAIL_RETCODE; } int od_modules_unload(od_logger_t *logger, od_module_t *modules) { char *err; od_list_t *i, *n; od_list_foreach_safe (&modules->link, i, n) { od_module_t *m; m = od_container_of(i, od_module_t, link); int rc; rc = m->unload_cb(); if (rc != OD_MODULE_CB_OK_RETCODE) { return -1; } void *h = m->handle; m->handle = NULL; od_module_free(m); /* because we cannot access handle after calling dlclose */ if (od_dlclose(h)) { goto error; } } return OD_MODULE_CB_OK_RETCODE; error: err = od_dlerror(); od_log(logger, "od unload module error", NULL, NULL, "od_module_unload: %s", err); return OD_MODULE_CB_FAIL_RETCODE; } int od_modules_unload_fast(od_module_t *modules) { od_list_t *i; od_list_foreach (&modules->link, i) { od_module_t *m; m = od_container_of(i, od_module_t, link); void *h = m->handle; m->handle = NULL; od_module_free(m); /* because we cannot access handle after calling dlclose */ if (od_dlclose(h)) { return OD_MODULE_CB_FAIL_RETCODE; } } return OD_MODULE_CB_OK_RETCODE; } odyssey-1.6.0-rc1/sources/multi_pool.c000066400000000000000000000156611526064202200177220ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include static inline void key_init(od_multi_pool_key_t *key) { key->dbname = NULL; key->username = NULL; od_address_init(&key->address); } static inline void key_destroy(od_multi_pool_key_t *key) { od_free(key->dbname); od_free(key->username); od_address_destroy(&key->address); } static inline int key_copy(od_multi_pool_key_t *dst, const od_multi_pool_key_t *src) { key_destroy(dst); key_init(dst); if (src->dbname != NULL) { dst->dbname = strdup(src->dbname); if (dst->dbname == NULL) { goto error; } } if (src->username != NULL) { dst->username = strdup(src->username); if (dst->username == NULL) { goto error; } } if (od_address_copy(&dst->address, &src->address) != OK_RESPONSE) { goto error; } return 0; error: key_destroy(dst); key_init(dst); return 1; } static inline int null_strcmp(const char *a, const char *b) { if (a == NULL && b == NULL) { return 0; } if (a == NULL) { return -1; } if (b == NULL) { return 1; } return strcmp(a, b); } static inline int key_cmp(const od_multi_pool_key_t *a, const od_multi_pool_key_t *b) { int rc; rc = null_strcmp(a->dbname, b->dbname); if (rc != 0) { return rc; } rc = null_strcmp(a->username, b->username); if (rc != 0) { return rc; } return od_address_cmp(&a->address, &b->address); } static inline od_multi_pool_element_t * od_multi_pool_element_create(atomic_uint_fast64_t *version) { od_multi_pool_element_t *element = od_malloc(sizeof(od_multi_pool_element_t)); if (element == NULL) { return NULL; } key_init(&element->key); od_server_pool_init(&element->pool); od_list_init(&element->link); mm_wait_list_init(&element->wait_bus, version); return element; } static inline void od_multi_pool_element_free(od_multi_pool_element_t *element, od_server_pool_free_fn_t free_fn) { key_destroy(&element->key); free_fn(&element->pool); mm_wait_list_destroy(&element->wait_bus); od_free(element); } od_multi_pool_t *od_multi_pool_create(od_server_pool_free_fn_t free_fn) { od_multi_pool_t *mpool = od_malloc(sizeof(od_multi_pool_t)); if (mpool == NULL) { return NULL; } atomic_init(&mpool->version, 0); mpool->pool_free_fn = free_fn; od_list_init(&mpool->pools); mm_mutex_init(&mpool->lock); return mpool; } void od_multi_pool_destroy(od_multi_pool_t *mpool) { od_list_t *i, *s; od_list_foreach_safe (&mpool->pools, i, s) { od_multi_pool_element_t *el; el = od_container_of(i, od_multi_pool_element_t, link); od_list_unlink(&el->link); od_multi_pool_element_free(el, mpool->pool_free_fn); } mm_mutex_destroy(&mpool->lock); od_free(mpool); } static inline od_multi_pool_element_t * od_multi_pool_get_internal(od_multi_pool_t *mpool, const od_multi_pool_key_t *key) { od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *element; element = od_container_of(i, od_multi_pool_element_t, link); if (key_cmp(&element->key, key) == 0) { return element; } } return NULL; } od_multi_pool_element_t * od_multi_pool_get_or_create_locked(od_multi_pool_t *mpool, const od_multi_pool_key_t *key) { od_multi_pool_element_t *el = NULL; el = od_multi_pool_get_internal(mpool, key); if (el == NULL) { od_multi_pool_element_t *new_el = od_multi_pool_element_create(&mpool->version); if (key_copy(&new_el->key, key) != 0) { od_multi_pool_element_free(new_el, mpool->pool_free_fn); return NULL; } od_list_append(&mpool->pools, &new_el->link); el = new_el; } return el; } od_server_t * od_multi_pool_foreach_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg, od_server_state_t state, od_server_pool_cb_t callback, void **argv) { od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *el; el = od_container_of(i, od_multi_pool_element_t, link); if (filter != NULL && !filter(farg, &el->key)) { continue; } od_server_t *server = od_server_pool_foreach(&el->pool, state, callback, argv); if (server != NULL) { return server; } } return NULL; } int od_multi_pool_count_active_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg) { int count = 0; od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *el; el = od_container_of(i, od_multi_pool_element_t, link); if (filter != NULL && !filter(farg, &el->key)) { continue; } count += od_server_pool_active(&el->pool); } return count; } int od_multi_pool_count_idle_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg) { int count = 0; od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *el; el = od_container_of(i, od_multi_pool_element_t, link); if (filter != NULL && !filter(farg, &el->key)) { continue; } count += od_server_pool_idle(&el->pool); } return count; } int od_multi_pool_total_locked(od_multi_pool_t *mpool, const od_multi_pool_key_filter_t filter, void *farg) { int count = 0; od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *el; el = od_container_of(i, od_multi_pool_element_t, link); if (filter != NULL && !filter(farg, &el->key)) { continue; } count += od_server_pool_total(&el->pool); } return count; } od_server_t *od_multi_pool_peek_any_locked(od_multi_pool_t *mpool, od_server_state_t state) { od_server_t *server = NULL; od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *el = od_container_of(i, od_multi_pool_element_t, link); server = od_pg_server_pool_next(&el->pool, state); if (server != NULL) { break; } } return server; } typedef struct { od_client_t *client; } server_awaiter_t; static void attach_freed_server_now(void *private, void *arg) { server_awaiter_t *awaiter = private; od_server_t *server = arg; if (awaiter == NULL || server == NULL) { return; } if (server->state != OD_SERVER_IDLE) { return; } od_client_t *client = awaiter->client; od_server_attach_client(server, client); } void od_multi_pool_signal_locked(od_multi_pool_t *mpool, od_multi_pool_element_t *e, od_server_t *server) { if (e != NULL && server != NULL && server->state != OD_SERVER_UNDEF) { atomic_fetch_add(&mpool->version, 1); mm_wait_list_notify_cb(&e->wait_bus, attach_freed_server_now, server); return; } od_list_t *i; od_list_foreach (&mpool->pools, i) { od_multi_pool_element_t *el = od_container_of(i, od_multi_pool_element_t, link); atomic_fetch_add(&mpool->version, 1); mm_wait_list_notify(&el->wait_bus); } } int od_multi_pool_wait(od_multi_pool_element_t *el, od_client_t *client, uint64_t version, uint32_t timeout_ms) { server_awaiter_t awaiter; awaiter.client = client; return mm_wait_list_compare_wait(&el->wait_bus, &awaiter, version, timeout_ms); } odyssey-1.6.0-rc1/sources/od_assert.c000066400000000000000000000035241526064202200175150ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #if defined(__GLIBC__) || defined(__APPLE__) #include #define HAVE_BACKTRACE 1 #endif #include #include #include #include #define OD_ASSERT_MAX_FRAMES 64 void od_assert_fail(const char *expr, const char *file, int line) { od_global_t *global = od_global_get(); od_instance_t *instance = global == NULL ? NULL : global->instance; static OD_THREAD_LOCAL char log_buf[4096]; memset(log_buf, 0, sizeof(log_buf)); #ifdef HAVE_BACKTRACE void *frames[OD_ASSERT_MAX_FRAMES]; int nframes = backtrace(frames, OD_ASSERT_MAX_FRAMES); /* * trim frames past coroutine entry point (mm_context_runner) * use a union to convert function pointer to void* without * triggering */ union { void (*fn)(void); void *ptr; } runner_addr = { .fn = mm_context_runner }; for (int i = 0; i < nframes; i++) { if (frames[i] == runner_addr.ptr) { nframes = i; break; } } char **symbols = backtrace_symbols(frames, nframes); char *log_buf_ptr = log_buf; char *log_buf_end = log_buf + sizeof(log_buf); for (int i = 0; i < nframes && log_buf_ptr < log_buf_end; i++) { log_buf_ptr += snprintf(log_buf_ptr, log_buf_end - log_buf_ptr, "#%d %s\n", i, symbols ? symbols[i] : "??"); } free(symbols); #else snprintf(log_buf, sizeof(log_buf), "(backtrace unavailable)\n"); #endif fprintf(stderr, "assertion failed: %s (%s:%d)\n%s\n", expr, file, line, log_buf); if (global != NULL && instance != NULL) { od_error(&instance->logger, "assert", NULL, NULL, "assertion failed: %s (%s:%d)\n%s\n", expr, file, line, log_buf); od_logger_flush(&instance->logger); } fflush(stderr); abort(); } void od_abort(void) { od_release_assert(0); OD_UNREACHABLE(); } odyssey-1.6.0-rc1/sources/od_memory.c000066400000000000000000000026671526064202200175330ustar00rootroot00000000000000#include #include #include #include static OD_THREAD_LOCAL od_linear_alloc_t *_current_linear_alloc = NULL; void od_set_thread_linear_alloc(od_linear_alloc_t *la) { _current_linear_alloc = la; } static od_linear_alloc_t *current_linear_alloc(void) { return _current_linear_alloc; } void *od_malloc(size_t size) { od_linear_alloc_t *la = current_linear_alloc(); if (la == NULL) { return mm_malloc(size); } return od_linear_alloc_alloc(la, size, 0); } void od_free(void *ptr) { od_linear_alloc_t *la = current_linear_alloc(); if (la == NULL) { mm_free(ptr); } else { od_linear_alloc_free(la, ptr); } } void *od_calloc(size_t nmemb, size_t size) { od_linear_alloc_t *la = current_linear_alloc(); if (la == NULL) { return mm_calloc(nmemb, size); } return od_linear_alloc_calloc(la, nmemb, size); } void *od_realloc(void *ptr, size_t size) { od_linear_alloc_t *la = current_linear_alloc(); if (la == NULL) { return mm_realloc(ptr, size); } return od_linear_alloc_realloc(la, ptr, size); } char *od_strdup(const char *s) { od_linear_alloc_t *la = current_linear_alloc(); if (la == NULL) { return mm_strdup(s); } return od_linear_alloc_strdup(la, s); } char *od_strndup(const char *s, size_t n) { od_linear_alloc_t *la = current_linear_alloc(); if (la == NULL) { return mm_strndup(s, n); } return od_linear_alloc_strndup(la, s, n); } odyssey-1.6.0-rc1/sources/option.c000066400000000000000000000056341526064202200170460ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include void od_print_version(void) { char features[128]; fill_supported_features_string(features, sizeof(features)); #ifdef ODYSSEY_VERSION_GIT printf("%s %s (git %s) %s%s\ncompiled by %s\n", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT, ODYSSEY_BUILD_TYPE, features, ODYSSEY_COMPILER_STRING); #else printf("%s %s %s%s\ncompiled by %s\n", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_BUILD_TYPE, features, ODYSSEY_COMPILER_STRING); #endif } int od_format_version_string(char *buf, size_t size) { #ifdef ODYSSEY_VERSION_GIT return od_snprintf(buf, size, "%s %s (git %s) %s, compiled by %s", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT, ODYSSEY_BUILD_TYPE, ODYSSEY_COMPILER_STRING); #else return od_snprintf(buf, size, "%s %s %s, compiled by %s", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_BUILD_TYPE, ODYSSEY_COMPILER_STRING); #endif } void od_print_help(const char *program) { printf("Usage: %s [OPTION...] /path/to/odyssey.conf\n", program); printf("Odyssey - scalable postgresql connection pooler\n\n"); printf(" --console Do not fork on startup\n"); printf(" --log_to_stdout Log to stdout\n"); printf(" --silent Do not log anything\n"); printf(" --test Configuration testing\n"); printf(" --verbose Log everything\n"); printf(" -V, --version Print program version\n"); printf(" -h, --help Give this help list\n"); printf(" -? Same as --help\n"); printf("Report bugs to or .\n"); } od_retcode_t od_apply_validate_cli_args(od_logger_t *logger, od_config_t *conf, od_arguments_t *args, od_rules_t *rules) { if (conf->daemonize && !args->console) { conf->daemonize |= args->console; } else { conf->daemonize = 0; } if (args->silent && args->verbose) { od_log(logger, "startup", NULL, NULL, "silent and verbose option both specified"); return NOT_OK_RESPONSE; } if (args->silent) { conf->log_debug = 0; conf->log_session = 0; conf->log_query = 0; conf->log_session = 0; conf->log_stats = 0; od_list_t *i; od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); rule->log_query = 0; rule->log_debug = 0; } } if (args->verbose) { conf->log_debug = 1; conf->log_session = 1; conf->log_query = 1; conf->log_session = 1; conf->log_stats = 1; od_list_t *i; od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); rule->log_query = 1; rule->log_debug = 1; } } if (args->log_stdout) { conf->log_to_stdout = 1; } return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/pam.c000066400000000000000000000062241526064202200163070ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include struct sss { char *psswd; char *res; }; static int od_pam_conversation(int msgc, const struct pam_message **msgv, struct pam_response **rspv, void *authdata) { od_pam_auth_data_t *auth_data = authdata; if (msgc < 1 || msgv == NULL) { return PAM_CONV_ERR; } *rspv = od_malloc(msgc * sizeof(struct pam_response)); if (*rspv == NULL) { return PAM_CONV_ERR; } memset(*rspv, 0, msgc * sizeof(struct pam_response)); int rc = PAM_SUCCESS; int counter = 0; for (; counter < msgc; counter++) { od_list_t *i; od_list_foreach (&auth_data->link, i) { od_pam_auth_data_t *param; param = od_container_of(i, od_pam_auth_data_t, link); if (param->msg_style == msgv[counter]->msg_style) { (*rspv)[counter].resp = od_strdup(param->value); break; } } if ((*rspv)[counter].resp == NULL) { rc = PAM_CONV_ERR; break; } } if (rc != PAM_SUCCESS) { for (; counter >= 0; counter--) { od_list_t *i; od_list_foreach (&auth_data->link, i) { od_pam_auth_data_t *param; param = od_container_of(i, od_pam_auth_data_t, link); if (param->msg_style == msgv[counter]->msg_style) { od_free((*rspv)[counter].resp); break; } } } od_free(*rspv); *rspv = NULL; } return rc; } int od_pam_auth(char *od_pam_service, char *usrname, od_pam_auth_data_t *auth_data, mm_io_t *io) { struct pam_conv conv = { od_pam_conversation, .appdata_ptr = auth_data, }; pam_handle_t *pamh = NULL; int rc; rc = pam_start(od_pam_service, usrname, &conv, &pamh); if (rc != PAM_SUCCESS) { goto error; } char peer[128]; od_getpeername(io, peer, sizeof(peer), 1, 0); rc = pam_set_item(pamh, PAM_RHOST, peer); if (rc != PAM_SUCCESS) { goto error; } rc = pam_authenticate(pamh, PAM_SILENT); if (rc != PAM_SUCCESS) { goto error; } rc = pam_acct_mgmt(pamh, PAM_SILENT); if (rc != PAM_SUCCESS) { goto error; } rc = pam_end(pamh, rc); if (rc != PAM_SUCCESS) { return -1; } return 0; error: pam_end(pamh, rc); return -1; } void od_pam_convert_passwd(od_pam_auth_data_t *d, char *passwd) { od_list_t *i; od_list_foreach (&d->link, i) { od_pam_auth_data_t *param = od_container_of(i, od_pam_auth_data_t, link); if (param->msg_style == PAM_PROMPT_ECHO_OFF) { param->value = od_strdup(passwd); } return; } od_pam_auth_data_t *passwd_data = od_malloc(sizeof(od_pam_auth_data_t)); passwd_data->msg_style = PAM_PROMPT_ECHO_OFF; passwd_data->value = od_strdup(passwd); od_list_append(&d->link, &passwd_data->link); } od_pam_auth_data_t *od_pam_auth_data_create(void) { od_pam_auth_data_t *d = (od_pam_auth_data_t *)od_malloc(sizeof(od_pam_auth_data_t)); if (d == NULL) { return NULL; } od_list_init(&d->link); return d; } void od_pam_auth_data_free(od_pam_auth_data_t *d) { od_list_t *i; od_list_foreach (&d->link, i) { od_pam_auth_data_t *current = od_container_of(i, od_pam_auth_data_t, link); od_free(current->value); } od_list_unlink(&d->link); od_free(d); } odyssey-1.6.0-rc1/sources/pid.c000066400000000000000000000020461526064202200163040ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include void od_pid_init(od_pid_t *pid) { pid->pid = getpid(); pid->restart_ppid = od_restart_get_ppid(); atomic_init(&pid->restart_new_pid, -1); pid->pid_len = od_snprintf(pid->pid_sz, sizeof(pid->pid_sz), "%d", (int)pid->pid); } void od_pid_restart_new_set(od_pid_t *p, pid_t pid) { atomic_store(&p->restart_new_pid, pid); } pid_t od_pid_restart_new_get(od_pid_t *p) { return atomic_load(&p->restart_new_pid); } int od_pid_create(od_pid_t *pid, char *path) { char buffer[32]; int size = od_snprintf(buffer, sizeof(buffer), "%d\n", pid->pid); int rc; rc = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (rc == -1) { return -1; } int fd = rc; rc = write(fd, buffer, size); if (rc != size) { close(fd); return -1; } rc = close(fd); return rc; } int od_pid_unlink(od_pid_t *pid, char *path) { (void)pid; int rc = unlink(path); return rc; } odyssey-1.6.0-rc1/sources/pool.c000066400000000000000000000051701526064202200165020ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include od_rule_pool_t *od_rule_pool_alloc(void) { od_rule_pool_t *pool; pool = od_malloc(sizeof(od_rule_pool_t)); if (pool == NULL) { return NULL; } memset(pool, 0, sizeof(od_rule_pool_t)); pool->discard = 1; pool->smart_discard = 0; pool->discard_query = NULL; pool->cancel = 1; pool->rollback = 1; pool->reserve_prepared_statement = 1; pool->server_pstmt_cache_size = 0; pool->reset_timeout_ms = 1000; /* 1 sec */ pool->notice_after_waiting_ms = -1; pool->attach_check = 1; pool->acquire_fail_fast = 0; return pool; } int od_rule_pool_free(od_rule_pool_t *pool) { if (pool->routing_type) { od_free(pool->routing_type); } if (pool->pool_type_str) { od_free(pool->pool_type_str); } if (pool->discard_query) { od_free(pool->discard_query); } od_free(pool); return OK_RESPONSE; } int od_rule_pool_compare(od_rule_pool_t *a, od_rule_pool_t *b) { /* pool */ if (a->pool_type != b->pool_type) { return 0; } /* pool routing */ if (a->routing != b->routing) { return 0; } /* size */ if (a->size != b->size) { return 0; } /* timeout */ if (a->timeout != b->timeout) { return 0; } /* ttl */ if (a->ttl != b->ttl) { return 0; } /* pool_discard */ if (a->discard != b->discard) { return 0; } /* cancel */ if (a->cancel != b->cancel) { return 0; } /* rollback*/ if (a->rollback != b->rollback) { return 0; } /* client idle timeout */ if (a->client_idle_timeout != b->client_idle_timeout) { return 0; } /* idle_in_transaction_timeout */ if (a->idle_in_transaction_timeout != b->idle_in_transaction_timeout) { return 0; } /* reserve_prepared_statement */ if (a->reserve_prepared_statement != b->reserve_prepared_statement) { return 0; } /* server_pstmt_cache_size */ if (a->server_pstmt_cache_size != b->server_pstmt_cache_size) { return 0; } if (a->notice_after_waiting_ms != b->notice_after_waiting_ms) { return 0; } if (a->min_size != b->min_size) { return 0; } if (a->pin_on_listen != b->pin_on_listen) { return 0; } if (a->attach_check != b->attach_check) { return 0; } return 1; } int od_rule_matches_client(od_rule_pool_t *pool, od_pool_client_type_t t) { switch (t) { case OD_POOL_CLIENT_INTERNAL: return pool->routing == OD_RULE_POOL_INTERNAL; case OD_POOL_CLIENT_EXTERNAL: return pool->routing == OD_RULE_POOL_CLIENT_VISIBLE; default: /* no matches */ return 0; } } int od_rule_pool_can_add(const od_rule_pool_t *pool, int size) { return pool->size == 0 || size < pool->size; } odyssey-1.6.0-rc1/sources/prom_metrics.c000066400000000000000000000305661526064202200202430ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #ifdef PROMHTTP_FOUND #include #if MHD_VERSION >= 0x00097002 enum MHD_Result od_prom_AcceptPolicyCallback(__attribute__((unused)) void *cls, __attribute__((unused)) const struct sockaddr *addr, __attribute__((unused)) socklen_t addrlen) #else int od_prom_AcceptPolicyCallback(void *cls, const struct sockaddr *addr, socklen_t addrlen) #endif { return MHD_YES; } static bool system_supports_ipv6() { int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0); if (sock < 0) { return false; /* cannot create IPv6 socket */ } struct sockaddr_in6 addr = { 0 }; addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_loopback; addr.sin6_port = htons(0); /* Let OS choose port */ int result = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); close(sock); return result == 0; } int od_prom_switch_server_on(od_prom_metrics_t *self) { int flags = MHD_USE_AUTO_INTERNAL_THREAD; if (system_supports_ipv6()) { flags |= MHD_USE_DUAL_STACK; } self->http_server = promhttp_start_daemon( flags, self->port, od_prom_AcceptPolicyCallback, NULL); return self->http_server ? OK_RESPONSE : NOT_OK_RESPONSE; } int od_prom_set_port(int port, od_prom_metrics_t *self) { if (!self) { return NOT_OK_RESPONSE; } if (port > 0 && !self->http_server) { self->port = port; } else { return port > 0 ? OK_RESPONSE : NOT_OK_RESPONSE; } return OK_RESPONSE; } int od_prom_activate_general_metrics(od_prom_metrics_t *self) { if (!self) { return NOT_OK_RESPONSE; } promhttp_set_active_collector_registry(self->stat_general_metrics); if (!self->http_server && self->port > 0) { return od_prom_switch_server_on(self); } else if (self->port <= 0) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } int od_prom_activate_route_metrics(od_prom_metrics_t *self) { if (!self) { return NOT_OK_RESPONSE; } promhttp_set_active_collector_registry(NULL); if (!self->http_server && self->port > 0) { return od_prom_switch_server_on(self); } else if (self->port <= 0) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } #endif int od_prom_metrics_init(struct od_prom_metrics *self) { if (self == NULL) { return 0; } self->stat_general_metrics = prom_collector_registry_new("stat_general_metrics"); prom_collector_t *stat_general_metrics_collector = prom_collector_new("stat_general_metrics_collector"); int err = prom_collector_registry_register_collector( self->stat_general_metrics, stat_general_metrics_collector); if (err) { return err; } self->database_len = prom_gauge_new("database_len", "Total databases count", 0, NULL); prom_collector_add_metric(stat_general_metrics_collector, self->database_len); self->server_pool_active = prom_gauge_new( "server_pool_active", "Active servers count", 0, NULL); prom_collector_add_metric(stat_general_metrics_collector, self->server_pool_active); self->server_pool_idle = prom_gauge_new("server_pool_idle", "Idle servers count", 0, NULL); prom_collector_add_metric(stat_general_metrics_collector, self->server_pool_idle); self->user_len = prom_gauge_new("user_len", "Total users count", 0, NULL); prom_collector_add_metric(stat_general_metrics_collector, self->user_len); prom_collector_t *stat_worker_metrics_collector = prom_collector_new("stat_metrics_collector"); err = prom_collector_registry_register_collector( self->stat_general_metrics, stat_worker_metrics_collector); if (err) { return err; } const char *worker_label[1] = { "worker" }; self->msg_allocated = prom_gauge_new( "msg_allocated", "Messages allocated", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->msg_allocated); self->msg_cache_count = prom_gauge_new( "msg_cache_count", "Messages cached", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->msg_cache_count); self->msg_cache_gc_count = prom_gauge_new( "msg_cache_gc_count", "Messages freed", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->msg_cache_gc_count); self->msg_cache_size = prom_gauge_new( "msg_cache_size", "Messages cache size", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->msg_cache_size); self->count_coroutine = prom_gauge_new( "count_coroutine", "Coroutines running", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->count_coroutine); self->count_coroutine_cache = prom_gauge_new( "count_coroutine_cache", "Coroutines cached", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->count_coroutine_cache); self->clients_processed = prom_gauge_new("clients_processed", "Number of processed clients", 1, worker_label); prom_collector_add_metric(stat_worker_metrics_collector, self->clients_processed); self->stat_route_metrics = prom_collector_registry_new("stat_route_metrics"); prom_collector_t *stat_database_metrics_collector = prom_collector_new("stat_database_metrics_collector"); err = prom_collector_registry_register_collector( self->stat_route_metrics, stat_database_metrics_collector); if (err) { return err; } const char *database_labels[1] = { "database" }; self->client_pool_total = prom_gauge_new("client_pool_total", "Total database clients count", 1, database_labels); prom_collector_add_metric(stat_database_metrics_collector, self->client_pool_total); prom_collector_t *stat_route_metrics_collector = prom_collector_new("stat_user_metrics_collector"); err = prom_collector_registry_register_collector( self->stat_route_metrics, stat_route_metrics_collector); if (err) { return err; } const char *user_labels[2] = { "user", "database" }; self->avg_tx_count = prom_gauge_new("avg_tx_count", "Average transactions count per second", 2, user_labels); prom_collector_add_metric(stat_route_metrics_collector, self->avg_tx_count); self->avg_tx_time = prom_gauge_new("avg_tx_time", "Average transaction time in usec", 2, user_labels); prom_collector_add_metric(stat_route_metrics_collector, self->avg_tx_time); self->avg_query_count = prom_gauge_new("avg_query_count", "Average query count per second", 2, user_labels); prom_collector_add_metric(stat_route_metrics_collector, self->avg_query_count); self->avg_query_time = prom_gauge_new( "avg_query_time", "Average query time in usec", 2, user_labels); prom_collector_add_metric(stat_route_metrics_collector, self->avg_query_time); self->avg_recv_client = prom_gauge_new( "avg_recv_client", "Average in bytes/sec", 2, user_labels); prom_collector_add_metric(stat_route_metrics_collector, self->avg_recv_client); self->avg_recv_server = prom_gauge_new( "avg_recv_server", "Average out bytes/sec", 2, user_labels); prom_collector_add_metric(stat_route_metrics_collector, self->avg_recv_server); prom_collector_registry_default_init(); prom_collector_registry_register_collector( PROM_COLLECTOR_REGISTRY_DEFAULT, stat_general_metrics_collector); prom_collector_registry_register_collector( PROM_COLLECTOR_REGISTRY_DEFAULT, stat_worker_metrics_collector); prom_collector_registry_register_collector( PROM_COLLECTOR_REGISTRY_DEFAULT, stat_database_metrics_collector); prom_collector_registry_register_collector( PROM_COLLECTOR_REGISTRY_DEFAULT, stat_route_metrics_collector); return 0; } int od_prom_metrics_write_stat(struct od_prom_metrics *self, u_int64_t msg_allocated, u_int64_t msg_cache_count, u_int64_t msg_cache_gc_count, u_int64_t msg_cache_size, u_int64_t count_coroutine, u_int64_t count_coroutine_cache) { if (self == NULL) { return 1; } const char *labels[1] = { "general" }; int err; err = prom_gauge_set(self->msg_allocated, (double)msg_allocated, labels); if (err) { return err; } err = prom_gauge_set(self->msg_cache_count, (double)msg_cache_count, labels); if (err) { return err; } err = prom_gauge_set(self->msg_cache_gc_count, (double)msg_cache_gc_count, labels); if (err) { return err; } err = prom_gauge_set(self->msg_cache_size, (double)msg_cache_size, labels); if (err) { return err; } err = prom_gauge_set(self->count_coroutine, (double)count_coroutine, labels); if (err) { return err; } err = prom_gauge_set(self->count_coroutine_cache, (double)count_coroutine_cache, labels); if (err) { return err; } return 0; } int od_prom_metrics_write_worker_stat( struct od_prom_metrics *self, int worker_id, u_int64_t msg_allocated, u_int64_t msg_cache_count, u_int64_t msg_cache_gc_count, u_int64_t msg_cache_size, u_int64_t count_coroutine, u_int64_t count_coroutine_cache, u_int64_t clients_processed) { if (self == NULL) { return 1; } char worker_label[12]; sprintf(worker_label, "worker[%d]", worker_id); const char *labels[1] = { worker_label }; int err; err = prom_gauge_set(self->msg_allocated, (double)msg_allocated, labels); if (err) { return err; } err = prom_gauge_set(self->msg_cache_count, (double)msg_cache_count, labels); if (err) { return err; } err = prom_gauge_set(self->msg_cache_gc_count, (double)msg_cache_gc_count, labels); if (err) { return err; } err = prom_gauge_set(self->msg_cache_size, (double)msg_cache_size, labels); if (err) { return err; } err = prom_gauge_set(self->count_coroutine, (double)count_coroutine, labels); if (err) { return err; } err = prom_gauge_set(self->count_coroutine_cache, (double)count_coroutine_cache, labels); if (err) { return err; } err = prom_gauge_set(self->clients_processed, (double)clients_processed, labels); if (err) { return err; } return 0; } const char *od_prom_metrics_get_stat(od_prom_metrics_t *self) { if (self == NULL) { return NULL; } return prom_collector_registry_bridge(self->stat_general_metrics); } int od_prom_metrics_write_stat_cb( od_prom_metrics_t *self, const char *user, const char *database, u_int64_t database_len, u_int64_t user_len, u_int64_t client_pool_total, u_int64_t server_pool_active, u_int64_t server_pool_idle, u_int64_t avg_tx_count, u_int64_t avg_tx_time, u_int64_t avg_query_count, u_int64_t avg_query_time, u_int64_t avg_recv_client, u_int64_t avg_recv_server) { if (self == NULL) { return 1; } const char *database_label[1] = { database }; const char *user_database_label[2] = { user, database }; int err = prom_gauge_set(self->database_len, (double)database_len, NULL); if (err) { return err; } err = prom_gauge_set(self->user_len, (double)user_len, NULL); if (err) { return err; } err = prom_gauge_set(self->client_pool_total, (double)client_pool_total, database_label); if (err) { return err; } err = prom_gauge_set(self->server_pool_active, (double)server_pool_active, NULL); if (err) { return err; } err = prom_gauge_set(self->server_pool_idle, (double)server_pool_idle, NULL); if (err) { return err; } err = prom_gauge_set(self->avg_tx_count, (double)avg_tx_count, user_database_label); if (err) { return err; } err = prom_gauge_set(self->avg_tx_time, (double)avg_tx_time, user_database_label); if (err) { return err; } err = prom_gauge_set(self->avg_query_count, (double)avg_query_count, user_database_label); if (err) { return err; } err = prom_gauge_set(self->avg_query_time, (double)avg_query_time, user_database_label); if (err) { return err; } err = prom_gauge_set(self->avg_recv_server, (double)avg_recv_server, user_database_label); if (err) { return err; } err = prom_gauge_set(self->avg_recv_client, (double)avg_recv_client, user_database_label); if (err) { return err; } return 0; } extern const char *od_prom_metrics_get_stat_cb(od_prom_metrics_t *self) { if (self == NULL) { return NULL; } return prom_collector_registry_bridge(self->stat_route_metrics); } extern int od_prom_metrics_destroy(od_prom_metrics_t *self) { if (self == NULL) { return 1; } prom_collector_registry_destroy(self->stat_general_metrics); self->stat_general_metrics = NULL; prom_collector_registry_destroy(self->stat_route_metrics); self->stat_route_metrics = NULL; od_free(self); return 0; } odyssey-1.6.0-rc1/sources/pstmt.c000066400000000000000000000514171526064202200167050ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include /* XXX: randomize seed ? */ static const uint64_t hash_seed = 0; /* * client hash map * "P_0" -> *od_prepared_stmt_t */ static mm_hash_t xxh_str_ptr(const void *data) { const char *ptr = *(const char **)data; return mm_xxh64_hash(ptr, strlen(ptr), hash_seed); } static int str_ptr_cmp(const void *k1, const void *k2) { const char *s1 = *(const char **)k1; const char *s2 = *(const char **)k2; return strcmp(s1, s2); } static void str_ptr_dtor(void *k) { od_free(*(char **)k); } static int str_ptr_copy(void *dst, const void *src) { const char *key = *(const char **)src; char *copy = od_strdup(key); if (copy == NULL) { return -1; } memcpy(dst, ©, sizeof(char *)); return 0; } mm_hashmap_t *od_client_pstmt_hashmap_create(void) { return mm_hashmap_create( 50 /* XXX: big enough? */, 0 /* nlocks = 0, map is client-private */, sizeof(char *) /* key size */, sizeof(od_pstmt_t *) /* val size */, str_ptr_cmp /* key cmp */, xxh_str_ptr /* key hash */, str_ptr_dtor /* key dtor */, NULL /* no need to free the pointer from global table */, str_ptr_copy /* key copy */); } static int unref_pstmt_entry(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp, void **argv) { (void)argv; od_pstmt_t *pstmt = *(od_pstmt_t **)mm_hashmap_kvp_val(hm, kvp); od_pstmt_unref(pstmt); return 0; } void od_client_pstmt_hashmap_free(mm_hashmap_t *hm) { mm_hashmap_foreach(hm, unref_pstmt_entry, NULL); mm_hashmap_free(hm); } od_pstmt_t *od_client_get_pstmt(od_client_t *client, const char *name) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(client->prep_stmt_ids, &klock, &name, 0 /* do not create */); (void)rc; if (klock.found) { od_pstmt_t *ps = *(od_pstmt_t **)mm_hashmap_kvp_val( client->prep_stmt_ids, klock.kvp); /* no real concurrent access - can unlock now and return */ mm_hashmap_unlock_key(client->prep_stmt_ids, &klock); return ps; } /* lock is not held if key not found */ return NULL; } int od_client_add_pstmt(od_client_t *client, const char *name, od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; od_pstmt_t *to_unref = NULL; const int unnamed = name[0] == '\0'; int rc = mm_hashmap_lock_key(client->prep_stmt_ids, &klock, &name, MM_HASHMAP_CREATE /* do create */); if (rc == -1) { /* cant create and lock is not held */ return rc; } int ret = 0; void *val = mm_hashmap_kvp_val(client->prep_stmt_ids, klock.kvp); if (unnamed && (*(void **)val) != NULL) { /* * its ok to redefine "" statement * (unnamed statemnt is rewritten every Parse, * not generate ErrorResponse about * statement already exists) * * so need to not forget to unref previous pstmt */ to_unref = *(od_pstmt_t **)val; } if (!klock.found || unnamed) { /* new or "" - save */ memcpy(val, &pstmt, sizeof(const od_pstmt_t *)); od_pstmt_ref(pstmt); ret = 0; } else { /* already exists - skip */ ret = 1; } /* no real concurrent access - can unlock now and return */ mm_hashmap_unlock_key(client->prep_stmt_ids, &klock); if (to_unref != NULL) { od_pstmt_unref(to_unref); } return ret; } int od_client_has_pstmt(od_client_t *client, const char *name) { return od_client_get_pstmt(client, name) != NULL; } int od_client_remove_pstmt(od_client_t *client, const char *name) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(client->prep_stmt_ids, &klock, &name, 0 /* do not create */); (void)rc; if (klock.kvp != NULL) { od_pstmt_t *pstmt = *(od_pstmt_t **)mm_hashmap_kvp_val( client->prep_stmt_ids, klock.kvp); mm_hashmap_remove(client->prep_stmt_ids, &klock); if (pstmt != NULL) { od_pstmt_unref(pstmt); } } /* if not found - lock is not held */ return 0; } void od_client_pstmts_clear(od_client_t *client) { mm_hashmap_foreach(client->prep_stmt_ids, unref_pstmt_entry, NULL); mm_hashmap_clear(client->prep_stmt_ids); } /* * client portal hash map * "portal_name" -> od_pstmt_t* * * portals are created by Bind (destination portal name) and destroyed by * Close Portal, transaction end, or DISCARD ALL / DEALLOCATE ALL. * * the value is a non-owning pointer into the global pstmt map. the pointer * is stable because the map holds its own reference while the entry exists * (entries are removed only when the last external reference is dropped, * see od_pstmt_unref). */ mm_hashmap_t *od_client_portal_hashmap_create(void) { return mm_hashmap_create( 50 /* XXX: big enough? */, 0 /* nlocks = 0, map is client-private */, sizeof(char *) /* key size */, sizeof(od_pstmt_t *) /* value size */, str_ptr_cmp /* key cmp */, xxh_str_ptr /* key hash */, str_ptr_dtor /* key dtor */, NULL /* no need to free the pointer from global table */, str_ptr_copy /* key copy */); } void od_client_portal_hashmap_free(mm_hashmap_t *hm) { mm_hashmap_foreach(hm, unref_pstmt_entry, NULL); mm_hashmap_free(hm); } od_pstmt_t *od_client_get_portal(od_client_t *client, const char *portal_name) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(client->portals, &klock, &portal_name, 0 /* do not create */); (void)rc; if (klock.found) { od_pstmt_t *ps = *(od_pstmt_t **)mm_hashmap_kvp_val( client->portals, klock.kvp); mm_hashmap_unlock_key(client->portals, &klock); return ps; } return NULL; } int od_client_add_portal(od_client_t *client, const char *portal_name, od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; od_pstmt_t *to_unref = NULL; int rc = mm_hashmap_lock_key(client->portals, &klock, &portal_name, MM_HASHMAP_CREATE /* do create */); if (rc == -1) { return rc; } /* * note: unnamed portal "" is silently overwritten on every Bind. * for named portals PG requires an explicit Close before re-Bind, but * the server will enforce that - we just store the mapping here */ void *val = mm_hashmap_kvp_val(client->portals, klock.kvp); to_unref = *(od_pstmt_t **)(val); memcpy(val, &pstmt, sizeof(const od_pstmt_t *)); od_pstmt_ref(pstmt); mm_hashmap_unlock_key(client->portals, &klock); if (to_unref != NULL) { od_pstmt_unref(to_unref); } return 0; } int od_client_remove_portal(od_client_t *client, const char *portal_name) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(client->portals, &klock, &portal_name, 0 /* do not create */); (void)rc; if (klock.kvp != NULL) { od_pstmt_t *pstmt = *(od_pstmt_t **)mm_hashmap_kvp_val( client->portals, klock.kvp); mm_hashmap_remove(client->portals, &klock); if (pstmt != NULL) { od_pstmt_unref(pstmt); } } return 0; } void od_client_portals_clear(od_client_t *client) { mm_hashmap_foreach(client->portals, unref_pstmt_entry, NULL); mm_hashmap_clear(client->portals); } /* * server hashmap * "odyssey_pstmt_0" -> od_server_pstmt_slot_t * * the slot embeds the SIEVE queue node and the visited bit */ typedef struct { /* borrowed: the hashmap entry owns the ref */ od_pstmt_t *pstmt; od_list_t link; int visited; } od_server_pstmt_slot_t; static int unref_server_pstmt_slot(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp, void **argv) { (void)argv; od_server_pstmt_slot_t *slot = mm_hashmap_kvp_val(hm, kvp); od_pstmt_unref(slot->pstmt); return 0; } static void server_pstmt_queue_reset(od_server_t *server) { od_list_init(&server->pstmt_fifo); server->pstmt_hand = &server->pstmt_fifo; server->pstmt_count = 0; } static mm_hash_t xxh_str_inplace(const void *data) { const char *key = data; return mm_xxh64_hash(data, strlen(key), hash_seed); } static int str_inplace_cmp(const void *k1, const void *k2) { const char *s1 = k1; const char *s2 = k2; return strcmp(s1, s2); } mm_hashmap_t *od_server_pstmt_hashmap_create(void) { return mm_hashmap_create( 1000 /* XXX: big enough? */, 1 /* nlocks = 1, console foreach may run concurrently with writers */, sizeof(od_pstmt_name_t) /* key size */, sizeof(od_server_pstmt_slot_t) /* value size */, str_inplace_cmp /* key cmp */, xxh_str_inplace /* key hash */, NULL /* no need to free on inplace str */, NULL /* no need to free the pointer from global table */, NULL /* no key copy function */ ); } void od_server_pstmts_free(od_server_t *server) { /* the hashmap has no value dtor, unref explicitly */ mm_hashmap_foreach(server->prep_stmts, unref_server_pstmt_slot, NULL); mm_hashmap_free(server->prep_stmts); server->prep_stmts = NULL; server_pstmt_queue_reset(server); } int od_server_has_pstmt(od_server_t *server, const od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(server->prep_stmts, &klock, pstmt->name, 0 /* do not create */); (void)rc; if (klock.found) { /* SIEVE hit */ od_server_pstmt_slot_t *slot = mm_hashmap_kvp_val(server->prep_stmts, klock.kvp); slot->visited = 1; /* no real concurrent access - can unlock now and return */ mm_hashmap_unlock_key(server->prep_stmts, &klock); return 1; } /* not exists - no lock held */ return 0; } int od_server_add_pstmt(od_server_t *server, od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(server->prep_stmts, &klock, pstmt->name, MM_HASHMAP_CREATE /* do create */); if (rc == -1) { /* cant create and lock is not held */ return rc; } /* * note: server's pstmt has generated name like odyssey_pstmt_1337 * so there is no unnamed statements at all and no possibility for unref * because no pstmts can be upserted */ int ret = 0; if (!klock.found) { od_server_pstmt_slot_t *slot = mm_hashmap_kvp_val(server->prep_stmts, klock.kvp); slot->pstmt = pstmt; slot->visited = 0; od_list_init(&slot->link); od_list_append(&server->pstmt_fifo, &slot->link); server->pstmt_count++; od_pstmt_ref(pstmt); ret = 0; } else { /* count a duplicate add as a hit */ od_server_pstmt_slot_t *slot = mm_hashmap_kvp_val(server->prep_stmts, klock.kvp); slot->visited = 1; ret = 1; } /* no real concurrent access - can unlock now and return */ mm_hashmap_unlock_key(server->prep_stmts, &klock); return ret; } int od_server_remove_pstmt(od_server_t *server, const od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(server->prep_stmts, &klock, pstmt->name, 0 /* do not create */); (void)rc; if (klock.found) { od_server_pstmt_slot_t *slot = mm_hashmap_kvp_val(server->prep_stmts, klock.kvp); /* the hand must not point at the node being unlinked */ if (server->pstmt_hand == &slot->link) { server->pstmt_hand = slot->link.next; } od_list_unlink(&slot->link); server->pstmt_count--; od_pstmt_t *p = slot->pstmt; mm_hashmap_remove(server->prep_stmts, &klock); if (p != NULL) { od_pstmt_unref(p); } } /* if not found - lock is not held */ return 0; } void od_server_pstmts_clear(od_server_t *server) { mm_hashmap_foreach(server->prep_stmts, unref_server_pstmt_slot, NULL); mm_hashmap_clear(server->prep_stmts); server_pstmt_queue_reset(server); } const od_pstmt_t *od_server_pstmt_kvp_pstmt(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp) { const od_server_pstmt_slot_t *slot = mm_hashmap_kvp_val_const(hm, kvp); return slot->pstmt; } int od_server_pstmt_evict_overflow(od_server_t *server, size_t cap, machine_msg_t *stream) { if (server->prep_stmts == NULL) { return 0; } int evicted = 0; /* three sweeps always reach a fixed point, see the SIEVE paper */ size_t iterations = 0; size_t bound = 3 * server->pstmt_count + 3; while (server->pstmt_count > cap && iterations++ < bound) { if (server->pstmt_hand == &server->pstmt_fifo) { server->pstmt_hand = server->pstmt_fifo.next; if (server->pstmt_hand == &server->pstmt_fifo) { break; } } od_server_pstmt_slot_t *slot = od_container_of( server->pstmt_hand, od_server_pstmt_slot_t, link); od_pstmt_t *pstmt = slot->pstmt; /* advance first: the node may be freed below */ server->pstmt_hand = slot->link.next; if (slot->visited) { slot->visited = 0; continue; } /* refs == 2: only the global map and this server hold it */ uint64_t refs = atomic_load_explicit(&pstmt->refs, memory_order_acquire); if (refs > 2) { continue; } mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(server->prep_stmts, &klock, pstmt->name, 0 /* do not create */); if (rc == -1) { continue; } if (!klock.found) { /* out of sync with the map - drop the node and its reference */ mm_hashmap_unlock_key(server->prep_stmts, &klock); od_list_unlink(&slot->link); server->pstmt_count--; od_pstmt_unref(pstmt); continue; } od_release_assert((od_server_pstmt_slot_t *)mm_hashmap_kvp_val( server->prep_stmts, klock.kvp) == slot); od_release_assert(((od_server_pstmt_slot_t *)mm_hashmap_kvp_val( server->prep_stmts, klock.kvp)) ->pstmt == pstmt); machine_msg_t *next = kiwi_fe_write_close( stream, 'S', pstmt->name, strlen(pstmt->name) + 1); if (next == NULL) { mm_hashmap_unlock_key(server->prep_stmts, &klock); return -1; } stream = next; od_list_unlink(&slot->link); server->pstmt_count--; mm_hashmap_remove(server->prep_stmts, &klock); /* last use of pstmt: the unref may free it */ od_pstmt_unref(pstmt); evicted++; } return evicted; } /* * global map * * key: od_pstmt_desc_t (inline, non-owning — desc.data points into * the value's desc.data, so the key is a "view" of the value) * value: od_pstmt_t (inline, owns its own copy of desc.data) */ static mm_hash_t xxh_pstmt_desc(const void *data) { const od_pstmt_desc_t *desc = data; return mm_xxh64_hash(desc->data, desc->len, hash_seed); } static int pstmt_desc_cmp(const void *k1, const void *k2) { const od_pstmt_desc_t *d1 = k1; const od_pstmt_desc_t *d2 = k2; if (d1->len != d2->len) { return 1; } return memcmp(d1->data, d2->data, d1->len); } static inline __attribute__((unused)) int pstmt_is_correct_deletion(od_pstmt_t *p) { /* * need to check ref correctly because pstmt has a race on creation * (can be created and then deleted immediately because some other * thread created same pstmt) * * so either it is the last ref (ref == 1) * or it is not initialized pstmt (ref == 0 and empty description) */ uint64_t r = atomic_load_explicit(&p->refs, memory_order_acquire); return r == 1 || (r == 0 && p->desc.data == NULL); } static void pstmt_desc_val_dtor(void *val) { od_pstmt_t *pstmt = val; od_assert(pstmt_is_correct_deletion(pstmt)); od_free(pstmt->desc.data); } static void pstmt_init_new(od_global_pstmt_map_t *hm, od_pstmt_t *out) { uint64_t num = atomic_fetch_add_explicit(&hm->counter, 1, memory_order_relaxed); od_release_assert(num <= OD_MAX_PSTMT_NUM); atomic_init(&out->refs, 1); out->source = hm; od_snprintf(out->name, sizeof(od_pstmt_name_t), "%s%" PRIu64, OD_PSTMT_NAME_PREFIX, num); } od_global_pstmt_map_t *od_global_pstmts_map_create(size_t nlocks) { mm_hashmap_t *hm = mm_hashmap_create( 10000 /* XXX: big enough? */, nlocks, sizeof(od_pstmt_desc_t) /* key size */, sizeof(od_pstmt_t) /* value size */, pstmt_desc_cmp /* key comparator */, xxh_pstmt_desc /* key hash */, NULL /* key does not own desc.data */, pstmt_desc_val_dtor /* value owns desc.data */, NULL /* no key copy — raw memcpy, data ptr overwritten after insert */ ); if (hm == NULL) { return NULL; } od_global_pstmt_map_t *gm = od_malloc(sizeof(od_global_pstmt_map_t)); if (gm == NULL) { mm_hashmap_free(hm); return NULL; } gm->hm = hm; atomic_init(&gm->counter, 0); return gm; } void od_global_pstmts_map_free(od_global_pstmt_map_t *hm) { mm_hashmap_free(hm->hm); od_free(hm); } od_pstmt_t *od_pstmt_create_or_get(od_global_pstmt_map_t *pstmts, const od_pstmt_desc_t desc, od_linear_alloc_t *arena) { mm_hashmap_keylock_t klock; int rc; od_pstmt_desc_t *key; od_pstmt_t *value; /* * lock_key will creates a key that is binary-copy of desc * * need to remember: it does not owns desc.data */ rc = mm_hashmap_lock_key(pstmts->hm, &klock, &desc, MM_HASHMAP_CREATE /* create if not exists */); if (rc == -1) { return NULL; } value = (od_pstmt_t *)mm_hashmap_kvp_val(pstmts->hm, klock.kvp); key = (od_pstmt_desc_t *)mm_hashmap_kvp_key(pstmts->hm, klock.kvp); if (!klock.found) { /* init new prep stmt */ memset(value, 0, sizeof(od_pstmt_t)); pstmt_init_new(pstmts, value); value->desc = od_pstmt_desc_copy(desc); if (value->desc.data == NULL) { mm_hashmap_remove(pstmts->hm, &klock); return NULL; } /* * rewrite the key's data pointer to point into the value * so the key becomes a non-owning view of the value, instead of * memcpy of find key (desc) */ key->data = value->desc.data; /* * parse the query text once and cache the query context flags * (is_discard_all, is_unlisten_all, is_deallocate_all, * deallocate_name) so that plan_execute / process_discard / * process_unlisten can avoid re-parsing on every Execute. * * od_sql_minimal_parse uses the arena provided by the caller; * the AST is discarded after extraction. deallocate_name is * stored inplace in query_ctx (no heap allocation). */ od_sql_minimal_node_t *ast = od_sql_minimal_parse(value->desc.data, strlen(value->desc.data), arena, NULL, NULL); od_sql_minimal_extract_query_ctx(ast, &value->query_ctx); } else { /* the key already exists and has a copy of desc.data, do nothing */ } /* the call-side now holds the ref too */ od_pstmt_ref(value); mm_hashmap_unlock_key(pstmts->hm, &klock); return value; } void od_global_pstmt_try_remove(od_global_pstmt_map_t *gm, od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; int rc; uint64_t refs; rc = mm_hashmap_lock_key(gm->hm, &klock, &pstmt->desc, 0 /* no create */); if (rc == -1) { return; } /* * every pstmt must be created from global hashmap, * so no need to check the klock.kvp != NULL */ od_assert(klock.kvp != NULL); od_assert((od_pstmt_t *)mm_hashmap_kvp_val(gm->hm, klock.kvp) == pstmt); /* * note: ref can be done only with the lock held (create_or_get) * or from the ref, that is already valid, so no race here */ refs = atomic_load_explicit(&pstmt->refs, memory_order_acquire); if (refs > 1) { mm_hashmap_unlock_key(gm->hm, &klock); } else { mm_hashmap_remove(gm->hm, &klock); } } int od_global_pstmts_has_pstmt(od_global_pstmt_map_t *gm, const od_pstmt_desc_t desc) { mm_hashmap_keylock_t klock; int rc; rc = mm_hashmap_lock_key(gm->hm, &klock, &desc, 0 /* do not create */); if (rc == -1) { return 0; } if (klock.found) { /* yes, not so thread-safe check, but this function is only used in tests */ mm_hashmap_unlock_key(gm->hm, &klock); return 1; } /* no lock held */ return 0; } static int foreach_wrapper(mm_hashmap_t *hm, mm_hashmap_kvp_t *kvp, void **argv) { union { od_global_pstmt_cb cb; void *p; } p; void *arg; p.p = argv[0]; arg = argv[1]; const od_pstmt_t *pstmt = (const od_pstmt_t *)mm_hashmap_kvp_val(hm, kvp); return p.cb(pstmt, arg); } void od_global_pstmt_foreach(od_global_pstmt_map_t *gm, od_global_pstmt_cb cb, void *arg) { union { od_global_pstmt_cb ccb; void *argv[2]; } a; a.ccb = cb; a.argv[1] = arg; mm_hashmap_foreach(gm->hm, foreach_wrapper, a.argv); } /* helpers */ char *od_pstmt_name_from_parse(machine_msg_t *msg) { char *data = machine_msg_data(msg); int size = machine_msg_size(msg); kiwi_prepared_statement_t desc; if (kiwi_be_read_parse_dest(data, size, &desc)) { return NULL; } return desc.operator_name; } od_pstmt_desc_t od_pstmt_desc_from_parse(machine_msg_t *msg) { od_pstmt_desc_t ret; memset(&ret, 0, sizeof(od_pstmt_desc_t)); char *data = machine_msg_data(msg); int size = machine_msg_size(msg); kiwi_prepared_statement_t desc; if (kiwi_be_read_parse_dest(data, size, &desc) == 0) { ret.data = desc.description; ret.len = desc.description_len; } return ret; } machine_msg_t *od_pstmt_parse_of(const od_pstmt_t *pstmt) { const char *srv_name = pstmt->name; const od_pstmt_desc_t *desc = &pstmt->desc; machine_msg_t *pmsg = kiwi_fe_write_parse_description( NULL, srv_name, strlen(srv_name) + 1 /* include zero byte */, desc->data, desc->len); return pmsg; } machine_msg_t *od_pstmt_describe_of(const od_pstmt_t *pstmt) { const char *srv_name = pstmt->name; machine_msg_t *dmsg = kiwi_fe_write_describe(NULL, 'S', srv_name, strlen(srv_name) + 1); return dmsg; } od_pstmt_desc_t od_pstmt_desc_copy(const od_pstmt_desc_t desc) { if (desc.data == NULL) { return desc; } od_pstmt_desc_t copy; memset(©, 0, sizeof(od_pstmt_desc_t)); copy.data = od_malloc(desc.len); if (copy.data != NULL) { copy.len = desc.len; memcpy(copy.data, desc.data, desc.len); return copy; } return copy; } odyssey-1.6.0-rc1/sources/query.c000066400000000000000000000056721526064202200167050ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include machine_msg_t *od_query_do(od_server_t *server, char *context, const char *query, char *param, uint32_t timeout_ms) { od_instance_t *instance = server->global->instance; od_debug(&instance->logger, context, server->client, server, "%s", query); if (od_backend_query_send(server, context, query, param, strlen(query) + 1, timeout_ms) == NOT_OK_RESPONSE) { return NULL; } machine_msg_t *ret_msg = NULL; machine_msg_t *msg; /* wait for response */ int has_result = 0; for (;;) { msg = od_read(&server->io, timeout_ms, OD_READ_BE); if (msg == NULL) { if (!machine_timedout()) { od_error(&instance->logger, context, server->client, server, "read error: %s", od_io_error(&server->io)); } if (ret_msg) { machine_msg_free(ret_msg); } return NULL; } int save_msg = 0; kiwi_be_type_t type; type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, context, server->client, server, "%s", kiwi_be_type_to_string(type)); switch (type) { case KIWI_BE_ERROR_RESPONSE: od_backend_error(server, context, machine_msg_data(msg), machine_msg_size(msg)); goto error; case KIWI_BE_ROW_DESCRIPTION: break; case KIWI_BE_DATA_ROW: { if (has_result) { goto error; } ret_msg = msg; has_result = 1; save_msg = 1; break; } case KIWI_BE_READY_FOR_QUERY: od_backend_ready(server, machine_msg_data(msg), machine_msg_size(msg)); machine_msg_free(msg); return ret_msg; default: break; } if (!save_msg) { machine_msg_free(msg); } } return ret_msg; error: machine_msg_free(msg); if (ret_msg) { machine_msg_free(ret_msg); } return NULL; } __attribute__((hot)) int od_query_format(char *format_pos, char *format_end, kiwi_var_t *user, char *peer, char *output, int output_len) { char *dst_pos = output; char *dst_end = output + output_len; while (format_pos < format_end) { if (*format_pos == '%') { format_pos++; if (od_unlikely(format_pos == format_end)) { break; } int len; switch (*format_pos) { case 'u': len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", user->value); dst_pos += len; break; case 'h': len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", peer); dst_pos += len; break; default: if (od_unlikely((dst_end - dst_pos) < 2)) { break; } dst_pos[0] = '%'; dst_pos[1] = *format_pos; dst_pos += 2; break; } } else { if (od_unlikely((dst_end - dst_pos) < 1)) { break; } dst_pos[0] = *format_pos; dst_pos += 1; } format_pos++; } if (od_unlikely((dst_end - dst_pos) < 1)) { return -1; } dst_pos[0] = 0; dst_pos++; return dst_pos - output; } odyssey-1.6.0-rc1/sources/rate.c000066400000000000000000000056711526064202200164720ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* * Implementation borrowed from golang x/rate. */ #include #include #include #include #include #include #include /* * Simple token-bucket rate limiter * * 1 token == 1e6. */ #define RATE_TOKEN_SCALE 1000000ULL /* * How many time we need to accumulate that many token. * Keep this as separate func to support bursts (later). */ static inline int64_t od_rate_duration_from_tokens(uint64_t limit, int64_t tokens) { od_assert(limit); return tokens / (int64_t)limit; } /* * Advance the limiter clock to `now` and refill tokens. */ static inline int64_t od_rate_advance(od_rate_limiter_t *lim, int64_t now) { int64_t last = lim->last; int64_t elapsed = now - last; /* CLOCK_MONOTONIC should guarantee < 0 is not possible. * anyway, check for zero this paranoic way. */ if (elapsed <= 0) { return lim->tokens; } /* new tokens = elapsed_usec * limit per sec */ int64_t delta = (elapsed * (int64_t)lim->limit); int64_t tokens = lim->tokens + delta; if (tokens > (int64_t)RATE_TOKEN_SCALE) { tokens = (int64_t)RATE_TOKEN_SCALE; } lim->tokens = tokens; lim->last = now; return tokens; } /* Limiter capacity is single token. limit is token * regenerate speed. For example, with lim = 100, two consecutive * waits will be trottled with 10ms. */ void od_rate_limiter_init(od_rate_limiter_t *lim, uint64_t limit) { lim->limit = limit; /* Fill just-created limiter with single token */ lim->tokens = RATE_TOKEN_SCALE; lim->last = machine_time_us(); mm_spinlock_init(&lim->lock); } void od_rate_limiter_destroy(od_rate_limiter_t *lim) { od_assert(lim); mm_spinlock_destroy(&lim->lock); } /* NB: all current users call this with n == 1. */ int od_rate_limiter_waitn(od_rate_limiter_t *lim, uint64_t n, uint32_t timeout_ms) { od_assert(lim); if (lim->limit == 0) { return -1; } int64_t end_us; if (timeout_ms == UINT32_MAX) { end_us = UINT64_MAX; } else { end_us = machine_time_us() + timeout_ms * 1000; } int64_t now_us; int64_t wait_usec = 0; for (;;) { /* XXX: check for cancellation here ? */ /* refresh and go */ now_us = machine_time_us(); if (now_us > end_us) { return -1; } mm_spinlock_lock(&lim->lock); int64_t tokens = od_rate_advance(lim, now_us); /* consume n tokens */ int64_t remaining = tokens - (int64_t)(n * RATE_TOKEN_SCALE); if (remaining >= 0) { lim->tokens = remaining; mm_spinlock_unlock(&lim->lock); return 0; } mm_spinlock_unlock(&lim->lock); /* not enough tokens: calculate how long to wait */ wait_usec = od_rate_duration_from_tokens(lim->limit, -remaining); uint32_t wait_ms = (uint32_t)(wait_usec / 1000); if (wait_ms == 0) { wait_ms = 1; } uint64_t remaining_ms = (end_us - now_us) / 1000; if (wait_ms >= remaining_ms) { return -1; } machine_sleep(wait_ms); } } odyssey-1.6.0-rc1/sources/readahead.c000066400000000000000000000070141526064202200174260ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include /* * Per-thread vrb cache. * * Each machinarium thread (worker, od_system, etc.) gets its own cache * instance, eliminating spinlock contention on the hot read/return path. * * Because od_readahead_prepare() is now only called from worker threads * (for both client and server io), every buffer is obtained from and * returned to the same per-thread cache — no cross-thread ownership transfer. */ static OD_THREAD_LOCAL mm_virtual_rbuf_cache_t *tls_vrb_cache = NULL; static OD_THREAD_LOCAL int tls_vrb_cache_initialized = 0; static void destroy_vrb_cache(void *arg) { mm_virtual_rbuf_cache_t *cache = (mm_virtual_rbuf_cache_t *)arg; if (cache == NULL) { return; } mm_virtual_rbuf_cache_destroy(cache); od_free(cache); tls_vrb_cache = NULL; tls_vrb_cache_initialized = 0; } static mm_virtual_rbuf_cache_t *get_vrb_cache(void) { if (tls_vrb_cache_initialized) { return tls_vrb_cache; } tls_vrb_cache_initialized = 1; mm_virtual_rbuf_cache_t *cache = od_malloc(sizeof(mm_virtual_rbuf_cache_t)); if (cache == NULL) { return NULL; } size_t size = (size_t)od_global_get_instance()->config.cache_coroutine; int rc = mm_virtual_rbuf_cache_init(cache, size); if (rc) { od_free(cache); tls_vrb_cache = NULL; od_gfatal("readahead", NULL, NULL, "can't create vrb cache, errno = %d (%s)", machine_errno(), strerror(machine_errno())); return NULL; } tls_vrb_cache = cache; /* destroy when this machinarium thread exits */ mm_machine_atexit(destroy_vrb_cache, cache); return cache; } int od_readahead_prepare(od_readahead_t *readahead) { mm_virtual_rbuf_cache_t *cache = get_vrb_cache(); if (cache == NULL) { return -1; } readahead->buf = mm_virtual_rbuf_cache_get(cache); if (readahead->buf != NULL) { return 0; } od_instance_t *instance = od_global_get_instance(); size_t size = (size_t)instance->config.readahead; readahead->buf = mm_virtual_rbuf_create(size); if (readahead->buf != NULL) { return 0; } return -1; } void od_readahead_free(od_readahead_t *readahead) { if (readahead->buf) { mm_virtual_rbuf_cache_t *cache = get_vrb_cache(); if (cache != NULL) { mm_virtual_rbuf_cache_put(cache, readahead->buf); } else { mm_virtual_rbuf_free(readahead->buf); } } } void od_readahead_describe(od_readahead_t *readahead, char *buf, size_t buf_size) { if (buf_size == 0) { return; } buf[0] = '\0'; int unread = od_readahead_unread(readahead); struct iovec rvec = od_readahead_read_begin(readahead); int written = snprintf(buf, buf_size, "readahead: %d bytes unread", unread); if (written < 0 || (size_t)written >= buf_size) { return; } const uint8_t *data = (const uint8_t *)rvec.iov_base; size_t avail = rvec.iov_len; size_t pos = 0; int msg_count = 0; while (pos + sizeof(kiwi_header_t) <= avail && msg_count < 10) { uint8_t type = data[pos]; uint32_t len; memcpy(&len, data + pos + 1, sizeof(len)); len = ntohl(len); if (len < 4) { break; } int w = snprintf(buf + written, buf_size - (size_t)written, "%s%c(len=%u)", msg_count == 0 ? "; msgs: " : ", ", (type >= 32 && type < 127) ? (char)type : '?', len); if (w < 0 || (size_t)w >= buf_size - (size_t)written) { break; } written += w; msg_count++; pos += 1 + (size_t)len; } } odyssey-1.6.0-rc1/sources/relay.c000066400000000000000000000644511526064202200166540ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define APPLICATION_NAME_STR "application_name" #define ODYSSEY_TARGET_SESSION_ATTRS_STR "odyssey.target_session_attrs" #define ODYSSEY_PIN_BACKEND "odyssey.pin_backend" #define ODYSSEY_OPPORTUNISTIC_ACQUIRE "odyssey.opportunistic_acquire" #define ODYSSEY_POOLING_MODE "odyssey.pooling_mode" #define ODYSSEY_VERSION_STR "odyssey.version" #define PROCESSED_BY_ODYSSEY_STR "processed virtually by odyssey" /* * relay - client messages handling subsystem * made generally for extended protocol * * simple protocol is handled in straight * * extended protocol is handled by putting the messages * in buffer, until Flush or Sync messages are met * * on flush and sync msgs the buffer are transformed * into plan, which is the msg sequence with inserted * Prepare msgs and virtual responses */ static void xbuf_msg_destroy(void *a) { od_xbuf_msg_t *m = a; machine_msg_free_safe(m->msg); } static void xbuf_init(od_relay_xbuf_t *xbuf) { mm_vector_init(&xbuf->msgs, sizeof(od_xbuf_msg_t), xbuf_msg_destroy); } static void xbuf_destroy_msgs(od_relay_xbuf_t *xbuf) { /* TODO: do not ignore error? */ mm_vector_clear(&xbuf->msgs); mm_vector_shrink_to_fit(&xbuf->msgs); } static void xbuf_destroy(od_relay_xbuf_t *xbuf) { mm_vector_destroy(&xbuf->msgs); } static int xbuf_append(od_relay_xbuf_t *xbuf, machine_msg_t *msg) { machine_msg_t *copy = machine_msg_copy(msg); if (copy == NULL) { return -1; } od_xbuf_msg_t xmsg; memset(&xmsg, 0, sizeof(od_xbuf_msg_t)); xmsg.msg = copy; int rc = mm_vector_append(&xbuf->msgs, &xmsg); if (rc != 0) { machine_msg_free(copy); } return rc; } static int xbuf_append_raw(od_relay_xbuf_t *xbuf, const void *data, size_t len) { machine_msg_t *copy = machine_msg_create(len); if (copy == NULL) { return -1; } char *d = machine_msg_data(copy); memcpy(d, data, len); od_xbuf_msg_t xmsg; memset(&xmsg, 0, sizeof(od_xbuf_msg_t)); xmsg.msg = copy; int rc = mm_vector_append(&xbuf->msgs, &xmsg); if (rc != 0) { machine_msg_free(copy); } return rc; } static void xbuf_clear(od_relay_xbuf_t *xbuf) { xbuf_destroy_msgs(xbuf); } void od_relay_init(od_relay_t *relay, od_client_t *client) { xbuf_init(&relay->xbuf); od_xplan_init(&relay->xplan); relay->client = client; relay->copy_additional = NULL; } void od_relay_destroy(od_relay_t *relay) { od_xplan_destroy(&relay->xplan); xbuf_destroy(&relay->xbuf); } static od_frontend_status_t reply_error_guc(od_client_t *client, const char *sqlstate, const char *buf, int len) { od_server_t *server; server = client->server; machine_msg_t *m = kiwi_be_write_notice(NULL, 'M', PROCESSED_BY_ODYSSEY_STR); if (m == NULL) { return OD_EOOM; } m = kiwi_be_write_error_as(m, "ERROR", sqlstate, NULL, 0, NULL, 0, buf, len); if (m == NULL) { return OD_EOOM; } int rc; rc = od_write2(&client->io, m, 1000); if (rc != 0) { return OD_ECLIENT_WRITE; } uint8_t txstatus = 'I'; if (server != NULL) { txstatus = server->is_transaction ? 'T' : 'I'; } m = kiwi_be_write_ready(NULL, txstatus); rc = od_write2(&client->io, m, 1000); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_SKIP; } static od_frontend_status_t reply_reject_guc(od_client_t *client, const char *guc_name, const char *option_value, size_t option_value_len) { char buf[128]; int len = od_snprintf(buf, sizeof(buf), "invalid value for parameter \"%s\": \"%.*s\"", guc_name, (int)option_value_len, option_value); return reply_error_guc(client, KIWI_INVALID_PARAMETER_VALUE, buf, len); } static od_frontend_status_t reply_cant_change_guc(od_client_t *client, const char *guc_name) { char buf[128]; int len = od_snprintf(buf, sizeof(buf), "parameter \"%s\" cannot be changed now", guc_name); return reply_error_guc(client, KIWI_CANT_CHANGE_RUNTIME_PARAM, buf, len); } static od_frontend_status_t process_set_generic_bool(od_client_t *client, const od_sql_minimal_set_stmt_t *stmt, bool *guc_val_ptr) { const char *option_value; size_t option_value_len; od_server_t *server; od_instance_t *instance = client->global->instance; server = client->server; option_value = stmt->value; option_value_len = strlen(option_value); if (strncasecmp(option_value, "true", option_value_len) == 0 || strncasecmp(option_value, "on", option_value_len) == 0 || strncasecmp(option_value, "1", option_value_len) == 0) { *guc_val_ptr = 1; } else if (strncasecmp(option_value, "false", option_value_len) == 0 || strncasecmp(option_value, "off", option_value_len) == 0 || strncasecmp(option_value, "0", option_value_len) == 0) { *guc_val_ptr = 0; } else { /* Reject this */ return reply_reject_guc(client, stmt->key, option_value, option_value_len); } /* XXX: refactor this */ od_debug(&instance->logger, "virtual processing", client, server, "processed virtual bool GUC %.*s", (int)option_value_len, option_value); uint8_t txstatus = 'I'; if (server != NULL) { txstatus = server->is_transaction ? 'T' : 'I'; } char msg[128 /* message below is ~ 60 bytes */]; int rc; char *out = msg; char *end = msg + sizeof(msg); rc = kiwi_be_format_notice(out, end - out, 'M', PROCESSED_BY_ODYSSEY_STR); od_assert(rc != -1); out += rc; rc = kiwi_be_format_command_complete(out, end - out, "SET"); od_assert(rc != -1); out += rc; rc = kiwi_be_format_ready(out, end - out, txstatus); od_assert(rc != -1); out += rc; size_t unused; rc = od_io_write_raw(&client->io, msg, out - msg, &unused, 1000, 0); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_SKIP; } static od_frontend_status_t process_set_tsa(od_client_t *client, const od_sql_minimal_set_stmt_t *stmt) { const char *option_value; size_t option_value_len; od_server_t *server; od_instance_t *instance = client->global->instance; server = client->server; option_value = stmt->value; option_value_len = strlen(option_value); /* for now, very straightforward logic, as there is only one supported param */ if (option_value_len == strlen("read-only") && strncasecmp(option_value, "read-only", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, "read-only", strlen("read-only")); } else if (option_value_len == strlen("read-write") && strncasecmp(option_value, "read-write", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, "read-write", strlen("read-write")); } else if (option_value_len == strlen("any") && strncasecmp(option_value, "any", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, "any", strlen("any")); } else if (option_value_len == strlen("prefer-standby") && strncasecmp(option_value, "prefer-standby", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, "prefer-standby", strlen("prefer-standby")); } else { od_debug(&instance->logger, "virtual processing", client, server, "unsupported tsa hint %.*s", (int)option_value_len, option_value); return reply_reject_guc(client, ODYSSEY_TARGET_SESSION_ATTRS_STR, option_value, option_value_len); } od_debug(&instance->logger, "virtual processing", client, server, "parsed tsa hint %.*s", (int)option_value_len, option_value); uint8_t txstatus = 'I'; if (server != NULL) { txstatus = server->is_transaction ? 'T' : 'I'; } char msg[128 /* message below is ~ 60 bytes */]; int rc; char *out = msg; char *end = msg + sizeof(msg); rc = kiwi_be_format_notice(out, end - out, 'M', PROCESSED_BY_ODYSSEY_STR); od_assert(rc != -1); out += rc; rc = kiwi_be_format_command_complete(out, end - out, "SET"); od_assert(rc != -1); out += rc; rc = kiwi_be_format_ready(out, end - out, txstatus); od_assert(rc != -1); out += rc; size_t unused; rc = od_io_write_raw(&client->io, msg, out - msg, &unused, 1000, 0); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_SKIP; } static od_frontend_status_t process_set_appname(od_client_t *client, const od_sql_minimal_set_stmt_t *stmt) { int rc; char original_appname[64]; size_t len = od_min(strlen(stmt->value), sizeof(original_appname)); snprintf(original_appname, sizeof(original_appname), "%s", stmt->value); char peer_name[KIWI_MAX_VAR_SIZE]; rc = od_getpeername(client->io.io, peer_name, sizeof(peer_name), 1, 0); if (rc != 0) { od_gerror("query", client, client->server, "can't get peer name, errno = %d (%s)", mm_errno_get(), strerror(mm_errno_get())); goto error; } if (client->server == NULL) { /* we will write to server - need to attach if not yet */ return OD_ATTACH; } char suffix[KIWI_MAX_VAR_SIZE]; od_snprintf(suffix, sizeof(suffix), " - %s", peer_name); char appname[64]; int appname_len = od_concat_prefer_right(appname, sizeof(appname), original_appname, len, suffix, strlen(suffix)); char query[128]; od_snprintf(query, sizeof(query), "set application_name to '%.*s';", appname_len, appname); machine_msg_t *msg; msg = kiwi_fe_write_query(NULL, query, strlen(query) + 1); if (msg == NULL) { od_gerror("query", client, client->server, "can't create message to send \"%s\"", query); return OD_EOOM; } rc = od_write(&client->server->io, msg); if (rc != 0) { od_gerror("query", client, client->server, "can't write \"%s\", rc = %d, errno = %d", query, rc, machine_errno()); return OD_ESERVER_WRITE; } od_server_sync_request(client->server, 1); return OD_REPLACED; error: /* can't handle, let pg do plain version of the query */ return OD_OK; } static od_frontend_status_t process_vset(od_client_t *client, const od_sql_minimal_set_stmt_t *stmt) { od_instance_t *instance = od_global_get_instance(); if (strcmp(stmt->key, APPLICATION_NAME_STR) == 0) { if (client->rule->application_name_add_host) { return process_set_appname(client, stmt); } } if (strcmp(stmt->key, ODYSSEY_TARGET_SESSION_ATTRS_STR) == 0) { if (instance->config.virtual_processing) { return process_set_tsa(client, stmt); } } if (strcmp(stmt->key, ODYSSEY_PIN_BACKEND) == 0) { if (instance->config.virtual_processing) { return process_set_generic_bool(client, stmt, &client->backend_pin); } } if (strcmp(stmt->key, ODYSSEY_OPPORTUNISTIC_ACQUIRE) == 0) { if (instance->config.virtual_processing) { return process_set_generic_bool( client, stmt, &client->opportunistic_acquire); } } if (strcmp(stmt->key, ODYSSEY_POOLING_MODE) == 0) { if (instance->config.virtual_processing) { return reply_cant_change_guc(client, stmt->key); } } return OD_OK; } static od_frontend_status_t virtual_str_ans(od_client_t *client, const char *name, const char *value) { machine_msg_t *stream = NULL; machine_msg_t *msg = NULL; int rc; int offset; uint8_t txstatus; od_server_t *server = client->server; stream = machine_msg_create(0); if (stream == NULL) { goto fail_oom; } msg = kiwi_be_write_notice(stream, 'M', PROCESSED_BY_ODYSSEY_STR); if (msg == NULL) { goto fail_oom; } msg = kiwi_be_write_row_descriptionf(stream, "s", name); if (msg == NULL) { goto fail_oom; } msg = kiwi_be_write_data_row(stream, &offset); if (msg == NULL) { goto fail_oom; } rc = kiwi_be_write_data_row_add(stream, offset, value, strlen(value)); if (rc != 0) { goto fail_oom; } rc = kiwi_be_write_complete(stream, "SHOW", 5); if (rc != 0) { goto fail_oom; } txstatus = 'I'; if (server != NULL) { txstatus = server->is_transaction ? 'T' : 'I'; } msg = kiwi_be_write_ready(stream, txstatus); if (msg == NULL) { goto fail_oom; } rc = od_write(&client->io, stream); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_SKIP; fail_oom: machine_msg_free(stream); return OD_EOOM; } static od_frontend_status_t process_show_tsa(od_client_t *client) { od_target_session_attrs_t tsa = od_tsa_get_effective(client); const char *val = od_target_session_attrs_to_str(tsa); return virtual_str_ans(client, ODYSSEY_TARGET_SESSION_ATTRS_STR, val); } static od_frontend_status_t process_show_bool_guc(od_client_t *client, const char *name, bool val) { return virtual_str_ans(client, name, val ? "on" : "off"); } static od_frontend_status_t process_show_pooling_mode(od_client_t *client) { const char *mode; switch (client->rule->pool->pool_type) { case OD_RULE_POOL_SESSION: mode = "session"; break; case OD_RULE_POOL_TRANSACTION: mode = "transaction"; break; case OD_RULE_POOL_STATEMENT: mode = "statement"; break; default: mode = "unknown"; break; } return virtual_str_ans(client, ODYSSEY_POOLING_MODE, mode); } static od_frontend_status_t process_show_version(od_client_t *client) { char data[128]; od_format_version_string(data, sizeof(data)); return virtual_str_ans(client, ODYSSEY_VERSION_STR, data); } static od_frontend_status_t process_vshow(od_client_t *client, const od_sql_minimal_show_stmt_t *stmt) { od_instance_t *instance = od_global_get_instance(); if (strcmp(stmt->name, ODYSSEY_TARGET_SESSION_ATTRS_STR) == 0) { if (instance->config.virtual_processing) { return process_show_tsa(client); } } if (strcmp(stmt->name, ODYSSEY_PIN_BACKEND) == 0) { if (instance->config.virtual_processing) { return process_show_bool_guc(client, stmt->name, client->backend_pin); } } if (strcmp(stmt->name, ODYSSEY_OPPORTUNISTIC_ACQUIRE) == 0) { if (instance->config.virtual_processing) { return process_show_bool_guc( client, stmt->name, client->opportunistic_acquire); } } if (strcmp(stmt->name, ODYSSEY_POOLING_MODE) == 0) { if (instance->config.virtual_processing) { return process_show_pooling_mode(client); } } if (strcmp(stmt->name, ODYSSEY_VERSION_STR) == 0) { if (instance->config.virtual_processing) { return process_show_version(client); } } return OD_OK; } static od_frontend_status_t process_vdeallocate(od_client_t *client, const od_sql_minimal_deallocate_stmt_t *deallocate) { od_instance_t *instance = client->global->instance; od_server_t *server = client->server; const char *command = NULL; if (deallocate->is_all) { od_debug(&instance->logger, "main", client, server, "DEALLOCATE ALL detected, remove from client hashmap"); od_client_pstmts_clear(client); od_client_portals_clear(client); command = "DEALLOCATE ALL"; } else { od_assert(deallocate->name != NULL); od_debug(&instance->logger, "main", client, server, "DEALLOCATE '%s' detected, remove from client hashmap", deallocate->name); od_client_remove_pstmt(client, deallocate->name); command = "DEALLOCATE"; } machine_msg_t *msg = kiwi_be_write_command_complete(NULL, command); if (msg == NULL) { return OD_EOOM; } uint8_t txstatus = 'I'; if (server != NULL) { txstatus = server->is_transaction ? 'T' : 'I'; } msg = kiwi_be_write_ready(msg, txstatus); if (msg == NULL) { return OD_EOOM; } if (od_write(&client->io, msg) != 0) { return OD_ECLIENT_WRITE; } return OD_SKIP; } static od_frontend_status_t process_vbegin(od_client_t *client, const od_sql_minimal_begin_stmt_t *stmt) { (void)stmt; od_server_t *server = client->server; int in_tx = (server != NULL && server->is_transaction); if (client->pending_begin || in_tx) { /* pass to pg and let it generate warning/error itself */ return OD_OK; } machine_msg_t *msg = kiwi_be_write_command_complete(NULL, "BEGIN"); if (msg == NULL) { return OD_EOOM; } msg = kiwi_be_write_ready(msg, 'T'); if (msg == NULL) { return OD_EOOM; } if (od_write(&client->io, msg) != 0) { return OD_ECLIENT_WRITE; } client->pending_begin = 1; return OD_SKIP; } static od_frontend_status_t try_virtual_process_query(od_client_t *client, od_linear_alloc_t *arena, const char *query, uint32_t query_len) { od_instance_t *instance = od_global_get_instance(); int need_process; if (instance->config.query_parsing.mode == OD_CONFIG_QUERY_PARSING_MODE_DISABLED) { return OD_OK; } od_sql_minimal_node_t *ast = od_sql_minimal_parse( query, query_len - 1 /* zero included */, arena, NULL, NULL); if (ast == NULL) { return OD_OK; } switch (ast->type) { case OD_SQL_MINIMAL_NODE_TYPE_SHOW_STMT: need_process = instance->config.virtual_processing; if (!need_process) { return OD_OK; } return process_vshow(client, (const od_sql_minimal_show_stmt_t *)ast); return OD_OK; case OD_SQL_MINIMAL_NODE_TYPE_SET_STMT: need_process = client->rule->application_name_add_host || instance->config.virtual_processing; if (!need_process) { return OD_OK; } return process_vset(client, (const od_sql_minimal_set_stmt_t *)ast); case OD_SQL_MINIMAL_NODE_TYPE_BEGIN_STMT: need_process = instance->config.virtual_transaction; if (!need_process) { return OD_OK; } return process_vbegin(client, (const od_sql_minimal_begin_stmt_t *)ast); case OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT: need_process = client->rule->pool->reserve_prepared_statement; if (!need_process) { return OD_OK; } return process_vdeallocate( client, (const od_sql_minimal_deallocate_stmt_t *)ast); case OD_SQL_MINIMAL_NODE_TYPE_UNLISTEN_STMT: case OD_SQL_MINIMAL_NODE_TYPE_DISCARD_STMT: od_sql_minimal_extract_query_ctx(ast, &client->query_ctx); /* let the backend execute the query */ return OD_OK; default: return OD_OK; } } typedef od_frontend_status_t (*handler_t)(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms); static od_frontend_status_t process_possible_attach(handler_t handler, od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_frontend_status_t status = handler(relay, msg, timeout_ms); if (status == OD_ATTACH) { od_client_t *client = relay->client; status = od_frontend_attach_and_deploy(client, "main"); if (status != OD_OK) { return status; } od_assert(client->server != NULL); /* to process the message, server was acquired, try again */ status = handler(relay, msg, timeout_ms); od_assert(status != OD_ATTACH); } return status; } static void process_discard(od_client_t *client, od_server_t *server) { od_route_t *route = client->route; od_instance_t *instance = client->global->instance; if (!route->rule->pool->reserve_prepared_statement) { return; } if (client->query_ctx.is_discard_all) { od_debug(&instance->logger, "main", client, server, "DISCARD ALL detected, invalidate caches"); od_client_pstmts_clear(client); od_client_portals_clear(client); if (server != NULL) { od_server_pstmts_clear(server); } } } static inline void process_unlisten(od_client_t *client, od_server_t *server) { od_route_t *route = client->route; od_instance_t *instance = client->global->instance; if (!route->rule->pool->pin_on_listen) { return; } if (client->query_ctx.is_unlisten_all) { od_debug(&instance->logger, "main", client, server, "UNLISTEN ALL detected, unpin client"); client->backend_pin = 0; } } static od_frontend_status_t proxy_until_command_complete(od_client_t *client, od_server_t *server, uint32_t timeout_ms) { int rc; int done = 0; machine_msg_t *msg = NULL; while (!done) { msg = od_read(&server->io, timeout_ms, OD_READ_BE); if (msg == NULL) { return OD_ESERVER_READ; } char *data = machine_msg_data(msg); int size = machine_msg_size(msg); kiwi_be_type_t type = *data; od_instance_t *instance = client->global->instance; if (instance->config.log_debug) { if (type == KIWI_BE_COMMAND_COMPLETE) { const char *command_tag = data + sizeof(kiwi_header_t); od_debug(&instance->logger, "main", client, server, "%s - %s", kiwi_be_type_to_string(type), command_tag); } else { od_debug(&instance->logger, "main", client, server, "%s", kiwi_be_type_to_string(type)); } } switch (type) { case KIWI_BE_COMMAND_COMPLETE: done = 1; machine_msg_free(msg); break; case KIWI_BE_PARAMETER_STATUS: rc = od_backend_update_parameter(server, "main", data, size, 0); if (rc == -1) { machine_msg_free(msg); od_gerror("main", client, server, "can't update parameter"); return OD_ESERVER_READ; } /* fallthrough */ default: rc = od_write(&client->io, msg); if (rc != 0) { return OD_ECLIENT_READ; } break; } } return OD_OK; } static od_frontend_status_t process_query_impl(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_client_t *client = relay->client; od_server_t *server = client->server; od_frontend_status_t status; char *data = machine_msg_data(msg); int size = machine_msg_size(msg); int rc; char *query; uint32_t query_len; rc = kiwi_be_read_query(data, size, &query, &query_len); if (rc != OK_RESPONSE) { od_gerror("main", client, server, "can't parse query message"); return OD_ESERVER_WRITE; } od_linear_alloc_t *arena = od_worker_get_local_linear_alloc(); status = try_virtual_process_query(client, arena, query, query_len); od_linear_alloc_reset(arena, 0); if (status == OD_SKIP) { /* query must not be sent to backend */ return OD_OK; } else if (status == OD_REPLACED) { /* replaced query was sent, wait rfq but no writing the query */ } else if (status == OD_OK) { if (server == NULL) { return OD_ATTACH; } if (client->pending_begin) { machine_msg_t *rewritten = kiwi_fe_write_query_join( NULL, "BEGIN;", query, NULL); if (rewritten == NULL) { return OD_EOOM; } rc = od_io_write(&server->io, rewritten, timeout_ms); machine_msg_free(rewritten); } else { rc = od_io_write(&server->io, msg, timeout_ms); } if (rc != 0) { return OD_ESERVER_WRITE; } od_server_sync_request(server, 1); } else { /* not skip, not replace and not ok - need handle on higher level */ return status; } od_stat_query_start(&server->stats_state); if (client->pending_begin) { /* * need to consume CommandComplete(BEGIN) * and maybe bypass some asynchronious messages */ status = proxy_until_command_complete(client, server, timeout_ms); if (status != OD_OK) { return status; } client->pending_begin = 0; } status = od_stream_server_until_rfq("main", server, timeout_ms); if (status == OD_COPY_IN_RECEIVED) { /* * server is awaiting CopyData from client - stream it */ machine_msg_t *add = od_relay_get_copy_additional(relay); od_assert(add == NULL); status = od_stream_copy_to_server("main", client, server, add, timeout_ms); } if (status == OD_OK) { /* process only after success query completion */ process_discard(client, server); process_unlisten(client, server); } return status; } uint8_t od_relay_deferred_begin_bytes[12] = { 'Q', 0, 0, 0, 11, 'B', 'E', 'G', 'I', 'N', ';', 0 }; static int relay_append(od_relay_t *relay, machine_msg_t *msg) { od_client_t *client = relay->client; if (client->pending_begin) { size_t len = sizeof(od_relay_deferred_begin_bytes); if (xbuf_append_raw(&relay->xbuf, od_relay_deferred_begin_bytes, len)) { return -1; } client->pending_begin = 0; } if (xbuf_append(&relay->xbuf, msg)) { return -1; } return 0; } /* note: does not free the buffers */ static od_frontend_status_t execute_xbuf(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_client_t *client = relay->client; od_server_t *server = client->server; od_frontend_status_t status; if (server == NULL) { /* we will write/read to/from server - attach if needed */ return OD_ATTACH; } if (relay_append(relay, msg)) { return OD_EOOM; } status = od_xplan_make_from_xbuf(&relay->xplan, relay); if (status != OD_OK) { return status; } status = od_xplan_run(&relay->xplan, relay, timeout_ms); return status; } static od_frontend_status_t process_fcall_impl(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_client_t *client = relay->client; od_server_t *server = client->server; if (server == NULL) { return OD_ATTACH; } /* no need special handling - just write call and wait for rfq */ int rc = od_io_write(&server->io, msg, timeout_ms); if (rc != 0) { return OD_ESERVER_WRITE; } od_server_sync_request(server, 1); od_stat_query_start(&server->stats_state); return od_stream_server_until_rfq("main", server, timeout_ms); } od_frontend_status_t od_relay_process_query(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_frontend_status_t status = process_possible_attach( process_query_impl, relay, msg, timeout_ms); od_query_ctx_reset(&relay->client->query_ctx); /* * in vanilla PG, executing simple query removes the * unnamed pstmt and unnamed portal on client */ if (relay->client->prep_stmt_ids != NULL) { od_client_remove_pstmt(relay->client, ""); } if (relay->client->portals != NULL) { od_client_remove_portal(relay->client, ""); } return status; } od_frontend_status_t od_relay_process_fcall(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_frontend_status_t status = process_possible_attach( process_fcall_impl, relay, msg, timeout_ms); return status; } od_frontend_status_t od_relay_process_xflush(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_frontend_status_t status = process_possible_attach(execute_xbuf, relay, msg, timeout_ms); /* never reuse this ones */ xbuf_clear(&relay->xbuf); od_xplan_clear(&relay->xplan); return status; } od_frontend_status_t od_relay_process_xsync(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { od_frontend_status_t status = process_possible_attach(execute_xbuf, relay, msg, timeout_ms); /* never reuse this ones */ xbuf_clear(&relay->xbuf); od_xplan_clear(&relay->xplan); return status; } od_frontend_status_t od_relay_process_xmsg(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { (void)timeout_ms; /* no any handling - real queries will be executed on flush/sync */ if (relay_append(relay, msg)) { return OD_EOOM; } return OD_OK; } odyssey-1.6.0-rc1/sources/reset.c000066400000000000000000000143741526064202200166610ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include int od_reset(od_server_t *server) { od_instance_t *instance = server->global->instance; od_route_t *route = server->route; if (server->oom) { od_log(&instance->logger, "reset", server->client, server, "server in oom mode, closing and drop connection"); goto drop; } if (server->cached_plan_broken) { od_log(&instance->logger, "reset", server->client, server, "server has broken cached plan, closing and drop connection"); goto drop; } /* server left in copy mode * check that number of received CopyIn/CopyOut Responses * is equal to number received CopyDone msgs. * it is indeed very strange situation if this numbers difference * is more that 1 (in absolute value). */ if (server->copy_mode) { od_log(&instance->logger, "reset", server->client, server, "server left in copy, closing and drop connection"); goto drop; } if (server->msg_broken) { od_log(&instance->logger, "reset", server->client, server, "server left with partly-read message, closing and drop connection"); goto drop; } if (server->xproto_mode) { od_log(&instance->logger, "reset", server->client, server, "server left in extended-query pipeline, closing and drop connection"); goto drop; } /* support route rollback off */ if (!route->rule->pool->rollback) { if (server->is_transaction) { od_log(&instance->logger, "reset", server->client, server, "in active transaction, closing"); goto drop; } } uint32_t reset_timeout_ms = UINT32_MAX; if (route->rule->pool->reset_timeout_ms > 0 && route->rule->pool->reset_timeout_ms < UINT32_MAX) { reset_timeout_ms = (uint32_t)route->rule->pool->reset_timeout_ms; } /* * Server is not synchronized. * * Number of queries sent to server is not equal * to the number of received replies. * Send cancel and try wait for synchronized state */ if (!od_server_synchronized(server)) { if (!route->rule->pool->cancel) { od_log(&instance->logger, "reset", server->client, server, "server left in query, closing"); goto drop; } /* * if query already completed on backend * then cancel will do nothing, so it is ok to send * cancel unconditionally */ int rc = od_cancel(server->global, server->endpoint->storage, od_server_pool_address(server), &server->key, &server->id); if (rc == NOT_OK_RESPONSE) { goto error; } od_frontend_status_t st = od_service_stream_server_until_rfq( "reset", server, 1 /* ignore_errors */, reset_timeout_ms); if (st != OD_OK) { od_log(&instance->logger, "reset", server->client, server, "rfq waiting failed - closing, status = %d (%s)", st, od_frontend_status_to_str(st)); goto drop; } /* still not synchronized - give up */ if (!od_server_synchronized(server)) { od_log(&instance->logger, "reset", server->client, server, "sync failed, server left in query (sync_reply = %" PRIu64 ", sync_request = %" PRIu64 ") - closing", server->sync_reply, server->sync_request); goto drop; } } od_debug(&instance->logger, "reset", server->client, server, "synchronized"); int rc; /* send rollback in case server has an active * transaction running */ if (route->rule->pool->rollback) { if (server->is_transaction) { char query_rlb[] = "ROLLBACK"; rc = od_backend_query(server, "reset-rollback", query_rlb, NULL, sizeof(query_rlb), reset_timeout_ms); if (rc == NOT_OK_RESPONSE) { goto error; } od_assert(!server->is_transaction); } } /* send DISCARD ALL */ if (route->rule->pool->discard) { char query_discard[] = "DISCARD ALL"; rc = od_backend_query(server, "reset-discard", query_discard, NULL, sizeof(query_discard), reset_timeout_ms); if (rc == NOT_OK_RESPONSE) { goto error; } } /* send smart discard */ if (route->rule->pool->smart_discard && route->rule->pool->discard_query == NULL) { char query_discard[] = "SET SESSION AUTHORIZATION DEFAULT;RESET ALL;CLOSE ALL;UNLISTEN *;SELECT pg_advisory_unlock_all();DISCARD PLANS;DISCARD SEQUENCES;DISCARD TEMP;"; rc = od_backend_query(server, "reset-discard-smart", query_discard, NULL, sizeof(query_discard), reset_timeout_ms); if (rc == NOT_OK_RESPONSE) { goto error; } } if (route->rule->pool->discard_query != NULL) { rc = od_backend_query(server, "reset-discard-smart-string", route->rule->pool->discard_query, NULL, strlen(route->rule->pool->discard_query) + 1, reset_timeout_ms); if (rc == NOT_OK_RESPONSE) { goto error; } } /* evict reserved prepared statements over the configured limit */ if (route->rule->pool->reserve_prepared_statement && route->rule->pool->server_pstmt_cache_size > 0 && server->pstmt_count > (size_t)route->rule->pool->server_pstmt_cache_size) { machine_msg_t *stream = machine_msg_create(0); if (stream == NULL) { goto error; } int evicted = od_server_pstmt_evict_overflow( server, (size_t)route->rule->pool->server_pstmt_cache_size, stream); if (evicted < 0) { machine_msg_free(stream); goto error; } if (evicted > 0) { machine_msg_t *sync = kiwi_fe_write_sync(stream); if (sync == NULL) { machine_msg_free(stream); goto error; } rc = od_io_write(&server->io, sync, reset_timeout_ms); machine_msg_free(sync); if (rc != 0) { goto error; } od_server_sync_request(server, 1); od_frontend_status_t st = od_service_stream_server_until_rfq( "reset", server, 1 /* ignore_errors */, reset_timeout_ms); if (st != OD_OK) { goto error; } } else { machine_msg_free(stream); } } if (od_readahead_unread(&server->io.readahead) > 0) { od_error( &instance->logger, "reset", server->client, server, "server left with some bytes in readahead after reset, closing and drop connection"); goto error; } /* ready */ return 1; drop: return 0; error: return -1; } odyssey-1.6.0-rc1/sources/restart_sync.c000066400000000000000000000063311526064202200202510ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #define ODYSSEY_PARENT_PID_ENV_NAME "ODY_INHERIT_PPID" pid_t od_restart_get_ppid(void) { const char *ppid_str = getenv(ODYSSEY_PARENT_PID_ENV_NAME); if (ppid_str == NULL) { return -1; } char *end; long pid = strtol(ppid_str, &end, 10); if (*end != 0) { /* finished not on terminate byte - some fmt error */ return -1; } return (pid_t)pid; } void send_sigterm_to_parent(void) { /* * if getppid has changed - parent already dead */ pid_t target = od_global_get_instance()->pid.restart_ppid; if (getppid() != target) { return; } online_restart_log("send SIGTERM to parent instance %d", target); kill(target, SIGTERM); } void od_restart_terminate_parent(void) { od_instance_t *instance = od_global_get_instance(); if (instance->pid.restart_ppid == -1) { /* Normal startup, not a restart - just notify systemd we're ready */ od_systemd_notify_ready(); return; } /* * Online restart scenario: send SIGTERM to parent. * Parent will notify systemd about the new main PID before it dies. * After parent exits, we notify systemd we're ready. */ static pthread_once_t parent_term_ctrl = PTHREAD_ONCE_INIT; (void)pthread_once(&parent_term_ctrl, send_sigterm_to_parent); /* * do not notify READY, need to wait for paren * to setup MAINPID first * * we will wait for SIGWINCH from parent, and then notify READY */ } char **build_envp(char *inherit_val) { od_instance_t *instance = od_global_get_instance(); char **envp = instance->cmdline.envp; size_t name_len = strlen(ODYSSEY_PARENT_PID_ENV_NAME); int existed_idx = -1; int count; for (count = 0; envp[count] != NULL; ++count) { if (existed_idx != -1) { continue; } int name_diff = strncmp(envp[count], ODYSSEY_PARENT_PID_ENV_NAME, name_len); if (name_diff == 0 && envp[count][name_len] == '=') { existed_idx = count; } } int new_count = (existed_idx == -1 ? count + 1 : count); char **new_envp = od_malloc(sizeof(char *) * (new_count + 1 /* for NULL */)); if (new_envp == NULL) { return NULL; } int i; for (i = 0; i < count; ++i) { if (i != existed_idx) { new_envp[i] = envp[i]; } else { new_envp[i] = inherit_val; } } if (existed_idx == -1) { new_envp[i++] = inherit_val; } new_envp[i] = NULL; return new_envp; } pid_t od_restart_run_new_binary(void) { char inherit_str[128]; snprintf(inherit_str, sizeof(inherit_str), "%s=%d", ODYSSEY_PARENT_PID_ENV_NAME, od_global_get_instance()->pid.pid); od_instance_t *instance = od_global_get_instance(); pid_t p = fork(); if (p == -1) { online_restart_error("can't fork new binary: %s", strerror(errno)); return -1; } if (p != 0) { /* report pid of new odyssey instance */ return p; } /* will not free any of allocations anyway */ char **envp = build_envp(inherit_str); if (envp != NULL) { execve(instance->cmdline.argv[0], instance->cmdline.argv, envp); } online_restart_error("can't start new binary: %s", strerror(errno)); abort(); } odyssey-1.6.0-rc1/sources/route.c000066400000000000000000000127201526064202200166660ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include od_multi_pool_element_t * od_route_get_server_pool_element_locked(od_route_t *route, const od_address_t *address) { od_multi_pool_key_t pool_key; memset(&pool_key, 0, sizeof(od_multi_pool_key_t)); memcpy(&pool_key.address, address, sizeof(od_address_t)); if (od_route_has_shared_pool(route)) { pool_key.dbname = route->id.database; pool_key.username = route->id.user; } return od_multi_pool_get_or_create_locked(od_route_server_pools(route), &pool_key); } static inline int pool_next_idle_exclusive_locked(od_route_t *route, const od_address_t *address, od_server_t **server) { od_assert(route->exclusive_pool != NULL); od_assert(route->shared_pool == NULL); od_multi_pool_element_t *pool_element = od_route_get_server_pool_element_locked(route, address); if (pool_element == NULL) { return OD_ROUTER_ERROR; } *server = od_pg_server_pool_next(&pool_element->pool, OD_SERVER_IDLE); return OD_ROUTER_OK; } static inline int pool_next_idle_shared_locked(od_route_t *route, od_storage_endpoint_t *endpoint, od_server_t **server) { od_assert(route->exclusive_pool == NULL); od_assert(route->shared_pool != NULL); od_multi_pool_element_t *pool_element = od_route_get_server_pool_element_locked(route, &endpoint->address); if (pool_element == NULL) { return OD_ROUTER_ERROR; } *server = od_pg_server_pool_next(&pool_element->pool, OD_SERVER_IDLE); if (*server != NULL) { return OD_ROUTER_OK; } /* * maybe we can replace some other mpool key's IDLE server? */ *server = od_multi_pool_peek_any_locked(route->shared_pool->mpool, OD_SERVER_IDLE); if (*server == NULL) { /* no idle servers at all */ return OD_ROUTER_OK; } od_assert((*server)->pool_element != pool_element); /* * the server can be replaced * - create new server, set it as IDLE * - close replaced * - TODO: do not do this with lock on route held * - TODO: maybe do not use some other's IDLE * if we have capacity to create new? */ od_server_t *new_server = od_server_allocate( route->rule->pool->reserve_prepared_statement); if (new_server == NULL) { return OD_ROUTER_ERROR; } od_id_generate(&new_server->id, "s"); new_server->global = od_global_get(); new_server->route = route; new_server->pool_element = pool_element; new_server->endpoint = endpoint; od_rules_storage_ref(endpoint->storage); od_backend_close_connection(*server); od_server_set_pool_state(*server, OD_SERVER_UNDEF); od_server_free(*server); od_server_set_pool_state(new_server, OD_SERVER_IDLE); *server = new_server; return OD_ROUTER_OK; } int od_route_server_pool_next_idle_locked(od_route_t *route, od_storage_endpoint_t *endpoint, od_server_t **server) { *server = NULL; int rc; if (od_route_has_exclusive_pool(route)) { rc = pool_next_idle_exclusive_locked(route, &endpoint->address, server); } else { rc = pool_next_idle_shared_locked(route, endpoint, server); } return rc; } static inline int filter_by_address(void *arg, const od_multi_pool_key_t *key) { const od_address_t *address = arg; if (od_address_cmp(address, &key->address) == 0) { return 1; } return 0; } int od_route_server_pool_total(od_route_t *route, od_multi_pool_element_t *el) { if (od_route_has_exclusive_pool(route)) { return od_server_pool_total(&el->pool); } int total = od_multi_pool_total_locked( route->shared_pool->mpool, filter_by_address, &el->key.address); return total; } int od_route_server_pool_can_add_locked(od_route_t *route, od_multi_pool_element_t *el) { if (od_route_has_exclusive_pool(route)) { return od_rule_pool_can_add(route->rule->pool, od_server_pool_total(&el->pool)); } int total = od_multi_pool_total_locked( route->shared_pool->mpool, filter_by_address, &el->key.address); /* shared pool can't have size of 0 */ return total < route->shared_pool->pool_size; } int od_route_wait(od_route_t *route, od_client_t *client, const od_address_t *address, uint64_t version, uint32_t timeout_ms) { od_route_lock(route); od_multi_pool_element_t *mpel = od_route_get_server_pool_element_locked(route, address); od_route_unlock(route); return od_multi_pool_wait(mpel, client, version, timeout_ms); } void od_route_signal_locked(od_route_t *route, od_server_t *server) { od_multi_pool_element_t *el = NULL; if (server != NULL) { const od_address_t *address = od_server_pool_address(server); el = od_route_get_server_pool_element_locked(route, address); } od_multi_pool_t *mpool = od_route_server_pools(route); od_multi_pool_signal_locked(mpool, el, server); } od_route_pswd_t *od_route_pswd_create(const char *value) { size_t len = value == NULL ? 0 : strlen(value); size_t t = sizeof(od_route_pswd_t) + len + 1; od_route_pswd_t *p = od_malloc(t); if (p == NULL) { return NULL; } memset(p, 0, t); atomic_store(&p->refs, 1); p->is_null = value == NULL; if (!p->is_null) { memcpy(p->value, value, len + 1); } return p; } od_route_pswd_t *od_route_pswd_ref(od_route_pswd_t *p) { if (p == NULL) { return NULL; } atomic_fetch_add(&p->refs, 1); return p; } void od_route_pswd_unref(od_route_pswd_t *p) { if (p == NULL) { return; } int64_t r = atomic_fetch_sub(&p->refs, 1); if (r == 1) { od_free(p); } } odyssey-1.6.0-rc1/sources/router.c000066400000000000000000000775051526064202200170640ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void od_router_init(od_router_t *router, od_global_t *global) { mm_mutex_init(&router->lock); od_rules_init(&router->rules); od_list_init(&router->servers); od_route_pool_init(&router->route_pool); atomic_init(&router->clients, 0); router->servers_routing = 0; router->global = global; router->router_err_logger = od_err_logger_create_default(); } static inline int od_router_immed_close_server_cb(od_server_t *server, void **argv) { (void)argv; /* remove server for server pool */ od_server_set_pool_state(server, OD_SERVER_UNDEF); server->route = NULL; od_backend_close_connection(server); od_backend_close(server); return 0; } static inline int od_router_immed_close_cb(od_route_t *route, void **argv) { od_route_lock(route); od_route_server_pool_foreach_locked( route, OD_SERVER_IDLE, od_router_immed_close_server_cb, argv); od_route_unlock(route); return 0; } void od_router_free(od_router_t *router) { od_list_t *i; od_list_t *n; od_list_foreach_safe (&router->servers, i, n) { od_system_server_t *server; server = od_container_of(i, od_system_server_t, link); od_system_server_free(server); } od_router_foreach(router, od_router_immed_close_cb, NULL); od_rules_free(&router->rules); od_route_pool_free(&router->route_pool); mm_mutex_destroy(&router->lock); od_err_logger_free(router->router_err_logger); } int od_router_foreach(od_router_t *router, od_route_pool_cb_t callback, void **argv) { od_router_lock(router); int rc; rc = od_route_pool_foreach(&router->route_pool, callback, argv); od_router_unlock(router); return rc; } static inline int od_drop_obsolete_rule_connections_cb(od_route_t *route, void **argv) { od_list_t *i; od_rule_t *rule = route->rule; od_list_t *obsolete_rules = argv[0]; od_list_foreach (obsolete_rules, i) { od_rule_key_t *obsolete_rule; obsolete_rule = od_container_of(i, od_rule_key_t, link); od_assert(rule); od_assert(obsolete_rule); if (strcmp(rule->user_name, obsolete_rule->usr_name) == 0 && strcmp(rule->db_name, obsolete_rule->db_name) == 0 && od_address_range_equals(&rule->address_range, &obsolete_rule->address_range)) { od_route_kill_client_pool(route); return 0; } } return 0; } int od_router_reconfigure(od_router_t *router, od_rules_t *rules) { od_instance_t *instance = router->global->instance; od_router_lock(router); int updates; od_list_t added; od_list_t deleted; od_list_t to_drop; od_list_t not_changed; od_list_t *i, *j; od_list_init(&added); od_list_init(&deleted); od_list_init(&to_drop); od_list_init(¬_changed); updates = od_rules_merge(&router->rules, rules, &added, &deleted, &to_drop, ¬_changed); od_list_foreach_safe (¬_changed, i, j) { od_rule_key_t *rk; rk = od_container_of(i, od_rule_key_t, link); od_debug(&instance->logger, "reload", NULL, NULL, "rule %s.%s %s %s was not changed", rk->db_name, rk->usr_name, rk->address_range.string_value, od_rule_conn_type_to_str(rk->conn_type)); od_rule_key_free(rk); } od_extension_t *extensions = router->global->extensions; od_module_t *modules = extensions->modules; od_list_foreach (&added, i) { od_rule_key_t *rk; rk = od_container_of(i, od_rule_key_t, link); od_log(&instance->logger, "reload", NULL, NULL, "rule %s.%s %s %s was added", rk->db_name, rk->usr_name, rk->address_range.string_value, od_rule_conn_type_to_str(rk->conn_type)); } od_list_foreach (&deleted, i) { od_rule_key_t *rk; rk = od_container_of(i, od_rule_key_t, link); od_log(&instance->logger, "reload", NULL, NULL, "rule %s %s %s %s was deleted", rk->db_name, rk->usr_name, rk->address_range.string_value, od_rule_conn_type_to_str(rk->conn_type)); } if (updates != 0) { void *argv[] = { &to_drop }; od_route_pool_foreach(&router->route_pool, od_drop_obsolete_rule_connections_cb, argv); } /* reloadcallback */ od_list_foreach (&modules->link, i) { od_module_t *module; module = od_container_of(i, od_module_t, link); if (module->od_config_reload_cb == NULL) { continue; } if (module->od_config_reload_cb(&added, &deleted) == OD_MODULE_CB_FAIL_RETCODE) { break; } } od_list_foreach_safe (&added, i, j) { od_rule_key_t *rk; rk = od_container_of(i, od_rule_key_t, link); od_rule_key_free(rk); } od_list_foreach_safe (&deleted, i, j) { od_rule_key_t *rk; rk = od_container_of(i, od_rule_key_t, link); od_rule_key_free(rk); } od_list_foreach_safe (&to_drop, i, j) { od_rule_key_t *rk; rk = od_container_of(i, od_rule_key_t, link); od_debug(&instance->logger, "reload", NULL, NULL, "rule %s.%s %s %s was dropped", rk->db_name, rk->usr_name, rk->address_range.string_value, od_rule_conn_type_to_str(rk->conn_type)); od_rule_key_free(rk); } od_router_unlock(router); return updates; } static inline int od_router_expire_server_cb(od_server_t *server, void **argv) { od_list_t *expire_list = argv[0]; int *count = argv[1]; /* remove server for server pool */ od_server_set_pool_state(server, OD_SERVER_UNDEF); od_list_append(expire_list, &server->link); (*count)++; return 0; } static inline int od_router_pool_ttl_expired(od_server_t *server) { od_route_t *route = server->route; if (route->rule->pool->ttl == 0 /* disabled */) { return 0; } return server->idle_time >= route->rule->pool->ttl; } static inline int od_router_lifetime_expired(od_server_t *server, uint64_t *now_us) { od_route_t *route = server->route; uint64_t max_lifetime = route->rule->server_lifetime_us; if (max_lifetime == 0 /* disabled */) { return 0; } uint64_t server_life = *now_us - server->init_time_us; return server_life >= max_lifetime; } static inline int od_router_server_expired(od_server_t *server, uint64_t *now_us) { return od_router_pool_ttl_expired(server) || od_router_lifetime_expired(server, now_us); } static inline int od_router_expire_server_tick_cb(od_server_t *server, void **argv) { od_list_t *expire_list = argv[0]; int *count = argv[1]; uint64_t *now_us = argv[2]; if (!server->offline) { int expired = od_router_server_expired(server, now_us); /* advance idle time for 1 sec */ if (!expired) { server->idle_time++; return 0; } /* * Do not expire more servers than we are allowed to connect at one time * This avoids need to re-launch lot of connections together */ if (*count > server->endpoint->storage->server_max_routing) { return 0; } } /* else remove server because we are forced to */ /* remove server for server pool */ od_server_set_pool_state(server, OD_SERVER_UNDEF); /* add to expire list */ od_list_append(expire_list, &server->link); (*count)++; return 0; } static inline int od_router_expire_cb(od_route_t *route, void **argv) { od_route_lock(route); /* expire by config obsoletion */ if (route->rule->obsolete && !od_client_pool_total(&route->client_pool)) { od_route_server_pool_foreach_locked(route, OD_SERVER_IDLE, od_router_expire_server_cb, argv); od_route_unlock(route); return 0; } int pool_ttl_disabled = route->rule->pool->ttl == 0; int server_lifetime_disabled = route->rule->server_lifetime_us == 0; if (pool_ttl_disabled && server_lifetime_disabled) { od_route_unlock(route); return 0; } od_route_server_pool_foreach_locked( route, OD_SERVER_IDLE, od_router_expire_server_tick_cb, argv); /* * maybe we have closed expired IDLE * connection and pool state has been changed */ od_route_signal_locked(route, NULL); od_route_unlock(route); return 0; } int od_router_expire(od_router_t *router, od_list_t *expire_list) { int count = 0; uint64_t now_us = machine_time_us(); void *argv[] = { expire_list, &count, &now_us }; od_router_foreach(router, od_router_expire_cb, argv); return count; } static inline od_server_t * od_router_create_connected_server(od_route_t *route, od_multi_pool_element_t *pool) { od_server_t *server = od_server_allocate( route->rule->pool->reserve_prepared_statement); if (server == NULL) { return NULL; } od_id_generate(&server->id, "s"); server->global = od_global_get(); server->route = route; server->pool_element = pool; od_rule_storage_t *storage = route->rule->storage; const od_address_t *address = od_server_pool_address(server); server->endpoint = od_storage_find_endpoint(storage, address); od_assert(server->endpoint != NULL); od_rules_storage_ref(server->endpoint->storage); /* connect is long operation, so release lock, which was taken by the caller */ od_route_unlock(route); int rc = od_backend_connect_to(server, "idle-preallocate", address, storage->tls_opts, OD_BACKEND_TLS_DEFAULT); /* caller function believes the lock is held */ od_route_lock(route); if (rc != OK_RESPONSE) { od_backend_close(server); return NULL; } /* * startup was not performed * must be done later by od_backend_startup_preallocated * after connect is taken from the pool */ return server; } static inline int od_router_create_idle_server(od_route_t *route, od_multi_pool_element_t *pool) { int min_pool_size = route->rule->pool->min_size; od_server_t *server = od_router_create_connected_server(route, pool); if (server == NULL) { return NOT_OK_RESPONSE; } if (od_server_pool_total(&pool->pool) >= min_pool_size) { /* * min pool size was reached in some other way * while we was connecting to server * * so just release this connection and exit */ od_backend_close_connection(server); od_backend_close(server); return NOT_OK_RESPONSE; } /* * detach from current thread event loop * need to do it manually, because we 'return' the connection to pool * manually, not by od_router_detach */ od_io_detach(&server->io); /* the pool still need in that connection */ od_server_set_pool_state(server, OD_SERVER_IDLE); od_logger_t *logger = &server->global->instance->logger; od_log(logger, "idle-preallocate", NULL, server, "idle server connection preallocated for %s.%s", route->id.database, route->id.user); return OK_RESPONSE; } /* returns number of allocated connections or -1 for errors */ static inline int od_router_keep_min_size_for_pool(od_multi_pool_element_t *element, od_route_t *route, int max_created) { int min_pool_size = route->rule->pool->min_size; if (od_server_pool_total(&element->pool) >= min_pool_size) { return 0; } int need = min_pool_size - od_server_pool_total(&element->pool); int created = 0; if (max_created > need) { max_created = need; } while (created < max_created) { int rc = od_router_create_idle_server(route, element); if (rc != OK_RESPONSE) { return -1; } ++created; } return created; } /* returns number of allocated connections or -1 for errors */ static inline int od_router_keep_min_pool_size_for_route(od_route_t *route, int max) { od_route_lock(route); /* preallocation doesn't work with shared_pools */ if (od_route_has_shared_pool(route)) { od_route_unlock(route); return 0; } od_rule_t *rule = route->rule; od_rule_storage_t *storage = rule->storage; od_storage_endpoint_t *endpoints = storage->endpoints; size_t count = storage->endpoints_count; int total = 0; for (size_t i = 0; i < count; ++i) { od_storage_endpoint_t *endpoint = &endpoints[i]; const od_address_t *address = &endpoint->address; od_multi_pool_key_t pool_key; memset(&pool_key, 0, sizeof(od_multi_pool_key_t)); memcpy(&pool_key.address, address, sizeof(od_address_t)); od_multi_pool_element_t *element = od_multi_pool_get_or_create_locked( od_route_server_pools(route), &pool_key); if (element == NULL) { od_route_unlock(route); return -1; } int count = od_router_keep_min_size_for_pool(element, route, max); if (count < 0) { od_route_unlock(route); return -1; } total += count; } od_route_unlock(route); return total; } /* returns number of allocated connections or -1 for errors */ int od_router_keep_min_pool_size_for_rule(od_router_t *router, od_rule_t *rule) { od_route_id_t id = { .database = rule->db_name, .user = rule->user_name, /* +1 here because it added in code above for storage_* */ .database_len = rule->db_name_len + 1, /* and because kiwi sets string length including 0-byte ? */ .user_len = rule->user_name_len + 1, .physical_rep = false, .logical_rep = false }; if (rule->storage_db) { id.database = rule->storage_db; id.database_len = strlen(rule->storage_db) + 1; } if (rule->storage_user) { id.user = rule->storage_user; id.user_len = strlen(rule->storage_user) + 1; } od_route_t *route; route = od_route_pool_match(&router->route_pool, &id, rule); if (route == NULL) { route = od_route_pool_new(&router->route_pool, &id, rule); if (route == NULL) { return -1; } } int allocated = od_router_keep_min_pool_size_for_route(route, 1); return allocated; } void od_router_keep_min_pool_size_step(od_router_t *router) { od_router_lock(router); /* * perform here a step only for one rule for connection allocations * to make sure we dont create much load * by allocation many connections simultaneously * * and to keep code simple, by not using wait group for every rule */ od_list_t *i; od_list_foreach (&router->rules.rules, i) { od_rule_t *rule = od_container_of(i, od_rule_t, link); /* disabled */ if (rule->pool->min_size == 0) { continue; } /* dont know for which user preallocate the connections */ if (rule->user_is_default || rule->db_is_default) { continue; } int allocated = od_router_keep_min_pool_size_for_rule(router, rule); /* allocated some connections or got error */ if (allocated != 0) { break; } } od_router_unlock(router); } static inline int od_router_gc_cb(od_route_t *route, void **argv) { od_route_pool_t *pool = argv[0]; od_route_lock(route); int nservers = od_route_server_pool_count_total_locked( route, 1 /* only for this route's db.user */); int nclients = od_client_pool_total(&route->client_pool); if (nservers > 0 || nclients > 0) { goto done; } if (route->auth_query_cache.refresh_in_progress) { goto done; } if (!od_route_is_dynamic(route) && !route->rule->obsolete) { goto done; } /* remove route from route pool */ od_assert(pool->count > 0); pool->count--; od_list_unlink(&route->link); od_route_unlock(route); od_route_free(route); return 0; done: od_route_unlock(route); return 0; } void od_router_gc(od_router_t *router) { void *argv[] = { &router->route_pool }; od_router_foreach(router, od_router_gc_cb, argv); } void od_router_stat(od_router_t *router, uint64_t prev_time_us, #ifdef PROM_FOUND od_prom_metrics_t *metrics, #endif od_route_pool_stat_cb_t callback, void **argv) { od_router_lock(router); od_route_pool_stat(&router->route_pool, prev_time_us, #ifdef PROM_FOUND metrics, #endif callback, argv); od_router_unlock(router); } od_router_status_t od_router_route(od_router_t *router, od_client_t *client) { kiwi_be_startup_t *startup = &client->startup; od_instance_t *instance = router->global->instance; struct sockaddr_storage sa; int salen; struct sockaddr *saddr; int rc; salen = sizeof(sa); saddr = (struct sockaddr *)&sa; if (client->type == OD_POOL_CLIENT_EXTERNAL) { rc = machine_getpeername(client->io.io, saddr, &salen); if (rc == -1) { return OD_ROUTER_ERROR; } } /* match route */ od_assert(startup->database.value_len); od_assert(startup->user.value_len); od_router_lock(router); /* match latest version of route rule */ od_rule_t *rule = NULL; /* initialize rule for (line 365) and flag '-Wmaybe-uninitialized' */ switch (client->type) { case OD_POOL_CLIENT_INTERNAL: rule = od_rules_forward(&router->rules, startup, NULL, 1); break; case OD_POOL_CLIENT_EXTERNAL: rule = od_rules_forward(&router->rules, startup, &sa, 0); break; case OD_POOL_CLIENT_UNDEF: /* create that case for correct work of '-Wswitch' flag */ break; } if (rule == NULL) { od_router_unlock(router); return OD_ROUTER_ERROR_NOT_FOUND; } od_debug(&instance->logger, "routing", NULL, NULL, "matching rule: %s %s %s with %s routing type to %s client", rule->db_name, rule->user_name, rule->address_range.string_value, rule->pool->routing_type == NULL ? "client visible" : rule->pool->routing_type, client->type == OD_POOL_CLIENT_INTERNAL ? "internal" : "external"); if (!od_rule_matches_client(rule->pool, client->type)) { /* emulate not found error */ od_router_unlock(router); return OD_ROUTER_ERROR_NOT_FOUND; } /* force settings required by route */ od_route_id_t id = { .database = startup->database.value, .user = startup->user.value, .database_len = startup->database.value_len, .user_len = startup->user.value_len, .physical_rep = client->physical_rep, .logical_rep = client->logical_rep }; if (rule->storage_db) { id.database = rule->storage_db; id.database_len = strlen(rule->storage_db) + 1; } if (rule->storage_user) { id.user = rule->storage_user; id.user_len = strlen(rule->storage_user) + 1; } #ifdef LDAP_FOUND if (rule->ldap_storage_credentials_attr) { od_rules_ref(rule); /* need to unlock - will do i/o operation */ od_router_unlock(router); int rc = od_auth_ldap_resolve_storage_credentials(client, rule); od_router_lock(router); od_rules_unref(rule); if (rc == OK_RESPONSE) { id.user = client->ldap_storage_username; id.user_len = client->ldap_storage_username_len + 1; if (rule->storage_user != NULL) { od_free(rule->storage_user); } rule->storage_user = od_strdup(client->ldap_storage_username); rule->storage_user_len = client->ldap_storage_username_len; if (rule->storage_password != NULL) { od_free(rule->storage_password); } rule->storage_password = od_strdup(client->ldap_storage_password); rule->storage_password_len = client->ldap_storage_password_len; od_debug(&instance->logger, "routing", client, NULL, "route->id.user changed to %s", id.user); } else { /* mask the possible insufficient access error */ od_router_unlock(router); return OD_ROUTER_ERROR_NOT_FOUND; } } #endif /* match or create dynamic route */ od_route_t *route; route = od_route_pool_match(&router->route_pool, &id, rule); if (route == NULL) { route = od_route_pool_new(&router->route_pool, &id, rule); /*od_debug() */ if (route == NULL) { od_router_unlock(router); return OD_ROUTER_ERROR; } } /* * we assign client's rule to pass connection limit to the place where * error is handled Client does not actually belong to the pool */ od_rules_ref(rule); client->rule = rule; od_route_lock(route); /* increase counter of new tot tcp connections */ ++route->tcp_connections; /* ensure route client_max limit */ if (rule->client_max_set && od_client_pool_total(&route->client_pool) >= rule->client_max) { od_route_unlock(route); od_router_unlock(router); od_router_status_t ret = OD_ROUTER_ERROR_LIMIT_ROUTE; if (route->extra_logging_enabled) { od_error_logger_store_err(route->err_logger, ret); } return ret; } od_router_unlock(router); /* add client to route client pool */ od_client_pool_set(&route->client_pool, client, OD_CLIENT_PENDING); client->route = route; od_route_unlock(route); return OD_ROUTER_OK; } void od_router_unroute(od_router_t *router, od_client_t *client) { (void)router; /* detach client from route */ od_assert(client->route); od_assert(client->server == NULL); od_route_t *route = client->route; od_route_lock(route); od_client_pool_set(&route->client_pool, client, OD_CLIENT_UNDEF); client->route = NULL; od_route_unlock(route); } bool od_should_not_spun_connection_yet(int connections_in_pool, int pool_size, int currently_routing, int max_routing) { if (pool_size == 0) { return currently_routing >= max_routing; } /* * This routine controls ramping of server connections. * When we have a lot of server connections we try to avoid opening new * in parallel. Meanwhile when we have no server connections we go at * maximum configured parallelism. * * This equation means that we gradually reduce parallelism until we reach * half of possible connections in the pool. */ max_routing = max_routing * (pool_size - connections_in_pool * 2) / pool_size; if (max_routing <= 0) { max_routing = 1; } return currently_routing >= max_routing; } #define MAX_BUZYLOOP_RETRY 10 static inline od_router_status_t od_router_try_create_new_server(od_router_t *router, od_client_t *client, od_storage_endpoint_t *endpoint, od_server_t **out_server) { od_server_t *server = NULL; od_route_t *route = client->route; od_rule_pool_t *rule_pool = route->rule->pool; int pool_size = rule_pool->size; if (od_route_has_shared_pool(route)) { pool_size = route->shared_pool->pool_size; } od_multi_pool_element_t *pool_element = od_route_get_server_pool_element_locked(route, &endpoint->address); if (pool_element == NULL) { od_route_unlock(route); return OD_ROUTER_ERROR; } *out_server = NULL; /* * Maybe start new connection, if pool_size allows it */ if (!od_route_server_pool_can_add_locked(route, pool_element)) { /* can't add new connection - wait for some released */ od_route_unlock(route); return OD_ROUTER_NEED_WAIT; } uint32_t max_routing = (uint32_t)endpoint->storage->server_max_routing; int busyloop_sleep = 0; int busyloop_retry = 0; while (od_should_not_spun_connection_yet( od_route_server_pool_total(route, pool_element), pool_size, (int)atomic_load(&router->servers_routing), (int)max_routing)) { /* concurrent server connection in progress. */ od_route_unlock(route); machine_sleep(busyloop_sleep); busyloop_retry++; /* TODO: support this opt in configure file */ if (busyloop_retry > MAX_BUZYLOOP_RETRY) { busyloop_sleep = 1; } od_route_lock(route); /* if the idle server was created while busyloop - lets use it */ int rc = od_route_server_pool_next_idle_locked(route, endpoint, &server); if (rc != OD_ROUTER_OK) { od_route_unlock(route); return rc; } if (server != NULL) { *out_server = server; return OD_ROUTER_OK; } } /* create new server object */ od_route_unlock(route); server = od_server_allocate( route->rule->pool->reserve_prepared_statement); if (server == NULL) { return OD_ROUTER_ERROR; } od_id_generate(&server->id, "s"); server->global = client->global; server->route = route; server->pool_element = pool_element; server->endpoint = endpoint; od_rules_storage_ref(endpoint->storage); od_route_lock(route); /* * pool size might have been changed by another workers * need to check it again and do wait if limit reached */ if (!od_route_server_pool_can_add_locked(route, pool_element)) { od_route_unlock(route); od_server_free(server); return OD_ROUTER_NEED_WAIT; } *out_server = server; return OD_ROUTER_OK; } static inline od_router_status_t od_router_try_attach(od_router_t *router, od_client_t *client, bool wait_for_idle, od_storage_endpoint_t *endpoint) { od_server_t *server; od_route_t *route = client->route; uint64_t now_us = machine_time_us(); od_route_lock(route); /* * stamp on the first transition only: the caller retries in a loop * and re-stamping would keep resetting the measured waiting time */ if (client->state != OD_CLIENT_QUEUE) { client->time_queue_start = now_us; } od_client_pool_set(&route->client_pool, client, OD_CLIENT_QUEUE); /* * try to get IDLE connection * if there are no free servers, then try to * create new */ int rc = od_route_server_pool_next_idle_locked(route, endpoint, &server); if (rc != OD_ROUTER_OK) { od_route_unlock(route); return rc; } if (server == NULL) { if (wait_for_idle) { /* * special case, when we are interested only in an idle connection * and do not want to start a new one */ int nactive = od_route_server_pool_count_active_locked( route, 0 /* not for only route's db.user in case of shared pools */); if (nactive == 0) { /* * there are no servers at all, so can't wait * for some ACTIVE that will become free */ od_route_unlock(route); return OD_ROUTER_ERROR_TIMEDOUT; } /* there is ACTIVE server that we can wait */ od_route_unlock(route); return OD_ROUTER_NEED_WAIT; } od_router_status_t st = od_router_try_create_new_server( router, client, endpoint, &server); if (st != OD_ROUTER_OK) { /* * od_router_try_create_new_server keeps lock held if everything ok * and unlocks it otherwise */ return st; } } od_assert(server != NULL); od_client_pool_set(&route->client_pool, client, OD_CLIENT_ACTIVE); od_server_attach_client(server, client); od_assert(od_server_synchronized(server)); /* * XXX: this logic breaks some external solutions that use * PostgreSQL logical replication. Need to tests this and fix * * walsender connections are in "graceful shutdown" mode since we cannot * reuse it. * if (client->physical_rep || client->logical_rep) server->offline = 1; */ od_route_unlock(route); /* attach server io to clients machine context */ if (server->io.io) { od_io_attach(&server->io); } return OD_ROUTER_OK; } static inline int send_waiting_notice(od_client_t *client) { char buf[64]; int n = kiwi_be_format_notice(buf, sizeof(buf), 'M', "waiting for free backend connection"); size_t unused; return od_io_write_raw(&client->io, buf, n, &unused, 1000, 0); } static inline int send_waiting_finished_notice(od_client_t *client, uint64_t time_spent, int timeout) { char msg[64]; char buf[64]; od_snprintf(msg, sizeof(msg), "waiting took %" PRIu64 " ms%s", time_spent, timeout ? ", timeout" : ""); int n = kiwi_be_format_notice(buf, sizeof(buf), 'M', msg); size_t unused; return od_io_write_raw(&client->io, buf, n, &unused, 1000, 0); } od_router_status_t od_router_attach(od_router_t *router, od_client_t *client, bool wait_for_idle, od_storage_endpoint_t *endpoint, int immediate) { od_route_t *route; od_router_status_t rc; uint64_t end_time_ms; route = client->route; od_assert(route != NULL); uint64_t now_ms = machine_time_ms(); if (route->rule->pool->timeout <= 0) { end_time_ms = UINT64_MAX; } else { end_time_ms = now_ms + (uint64_t)route->rule->pool->timeout; } int internal = route->rule->pool->routing == OD_RULE_POOL_INTERNAL; int notice_sent = 0; int notice_sending = route->rule->pool->notice_after_waiting_ms >= 0 && !internal; uint64_t notice_deadline = now_ms + route->rule->pool->notice_after_waiting_ms; uint64_t waiting_start = now_ms; od_router_status_t status = OD_ROUTER_ERROR_TIMEDOUT; while (now_ms < end_time_ms) { uint64_t version = od_route_pools_version(route); rc = od_router_try_attach(router, client, wait_for_idle, endpoint); if (rc != OD_ROUTER_NEED_WAIT) { /* ok or some other error */ status = rc; break; } if (rc == OD_ROUTER_NEED_WAIT && immediate) { /* no need to stat waiting time or send notice */ return OD_ROUTER_ERROR_TIMEDOUT; } if (notice_sending && !notice_sent && machine_time_ms() >= notice_deadline) { if (send_waiting_notice(client) != 0) { status = OD_ROUTER_CLIENT_DISCONNECTED; break; } notice_sent = 1; } uint32_t until_notice = 1000; if (notice_sending && !notice_sent) { until_notice = od_min(until_notice, notice_deadline - machine_time_ms()); } od_route_wait(route, client, &endpoint->address, version, od_min(end_time_ms - now_ms, until_notice)); if (client->server != NULL) { /* someone attached freed server directly to us */ od_route_lock(route); od_client_pool_set(&route->client_pool, client, OD_CLIENT_ACTIVE); od_route_unlock(route); if (client->server->io.io) { od_io_attach(&client->server->io); } status = OD_ROUTER_OK; break; } /* client disconnected while awaiting for attaching */ if (!internal && !od_io_connected(&client->io)) { return OD_ROUTER_CLIENT_DISCONNECTED; } now_ms = machine_time_ms(); } uint64_t wait_time = machine_time_ms() - waiting_start; od_stat_wait_time(&route->stats, wait_time * 1000 /* convert to us */); if ((status == OD_ROUTER_OK || status == OD_ROUTER_ERROR_TIMEDOUT) && notice_sent) { if (send_waiting_finished_notice( client, wait_time, status == OD_ROUTER_ERROR_TIMEDOUT) != 0) { status = OD_ROUTER_CLIENT_DISCONNECTED; } } return status; } void od_router_detach(od_router_t *router, od_client_t *client) { (void)router; od_route_t *route = client->route; od_assert(route != NULL); od_instance_t *instance = client->global->instance; /* detach from current machine event loop */ od_server_t *server = client->server; od_server_t *to_close = NULL; od_assert(server != NULL); od_assert(od_server_synchronized(server)); od_io_detach(&server->io); od_route_lock(route); int is_repl = client->physical_rep || client->logical_rep; int offline = server->offline; /* also sets server to IDLE */ od_server_detach_client(server); if (offline || is_repl) { server->route = NULL; od_server_set_pool_state(server, OD_SERVER_UNDEF); to_close = server; server = NULL; } od_client_pool_set(&route->client_pool, client, OD_CLIENT_PENDING); od_route_signal_locked(route, server); od_route_unlock(route); if (to_close != NULL) { if (is_repl) { od_debug(&instance->logger, "expire-replication", NULL, to_close, "closing replication connection"); } else if (offline) { od_debug(&instance->logger, "expire", NULL, to_close, "closing obsolete server connection"); } od_backend_close_connection(to_close); od_backend_close(to_close); } } void od_router_close(od_router_t *router, od_client_t *client) { (void)router; od_route_t *route = client->route; od_assert(route != NULL); od_server_t *server = client->server; od_backend_close_connection(server); od_route_lock(route); od_client_pool_set(&route->client_pool, client, OD_CLIENT_PENDING); od_server_detach_client(server); od_server_set_pool_state(server, OD_SERVER_UNDEF); server->route = NULL; od_route_signal_locked(route, NULL); od_route_unlock(route); od_assert(server->io.io == NULL); od_server_free(server); } static inline int od_router_cancel_cmp(od_server_t *server, void **argv) { /* check that server is attached and has corresponding cancellation key */ return (server->client != NULL) && kiwi_key_cmp(&server->key_client, argv[0]); } static inline int od_router_cancel_cb(od_route_t *route, void **argv) { od_route_lock(route); od_server_t *server; server = od_route_server_pool_foreach_locked( route, OD_SERVER_ACTIVE, od_router_cancel_cmp, argv); if (server) { od_router_cancel_t *cancel = argv[1]; cancel->id = server->id; cancel->key = server->key; cancel->storage = od_rules_storage_ref(server->endpoint->storage); cancel->address = od_server_pool_address(server); cancel->server = server; od_server_cancel_begin(server); od_route_unlock(route); return 1; } od_route_unlock(route); return 0; } od_router_status_t od_router_cancel(od_router_t *router, kiwi_key_t *key, od_router_cancel_t *cancel) { /* match server by client forged key */ void *argv[] = { key, cancel }; int rc; rc = od_router_foreach(router, od_router_cancel_cb, argv); if (rc <= 0) { return OD_ROUTER_ERROR_NOT_FOUND; } return OD_ROUTER_OK; } static inline int od_router_kill_cb(od_route_t *route, void **argv) { od_route_lock(route); od_route_kill_client(route, argv[0]); od_route_unlock(route); return 0; } void od_router_kill(od_router_t *router, od_id_t *id) { void *argv[] = { id }; od_router_foreach(router, od_router_kill_cb, argv); } odyssey-1.6.0-rc1/sources/rules.c000066400000000000000000002250371526064202200166710ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char *od_rule_conn_type_to_str(od_rule_conn_type_t ct) { switch (ct) { case OD_RULE_CONN_TYPE_DEFAULT: return "all"; case OD_RULE_CONN_TYPE_LOCAL: return "local"; case OD_RULE_CONN_TYPE_HOST: return "host"; case OD_RULE_CONN_TYPE_HOSTSSL: return "hostssl"; case OD_RULE_CONN_TYPE_HOSTNOSSL: return "hostnossl"; default: abort(); } } void od_rules_init(od_rules_t *rules) { mm_mutex_init(&rules->mu); od_list_init(&rules->storages); #ifdef LDAP_FOUND od_list_init(&rules->ldap_endpoints); #endif od_list_init(&rules->rules); rules->next_order = 0; } void od_rules_rule_free(od_rule_t *); void od_rules_free(od_rules_t *rules) { mm_mutex_destroy(&rules->mu); od_list_t *i, *n; #ifdef LDAP_FOUND od_list_foreach_safe (&rules->ldap_endpoints, i, n) { od_ldap_endpoint_t *ldap_endp; ldap_endp = od_container_of(i, od_ldap_endpoint_t, link); od_ldap_endpoint_free(ldap_endp); } #endif od_list_foreach_safe (&rules->rules, i, n) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); od_list_unlink(&rule->link); od_list_init(&rule->link); od_rules_rule_free(rule); } } #ifdef LDAP_FOUND od_ldap_endpoint_t *od_rules_ldap_endpoint_add(od_rules_t *rules, od_ldap_endpoint_t *ldap) { od_list_append(&rules->ldap_endpoints, &ldap->link); return ldap; } od_ldap_storage_credentials_t * od_rule_ldap_storage_credentials_add(od_rule_t *rule, od_ldap_storage_credentials_t *lsc) { od_ldap_storage_credentials_ref(lsc); od_list_append(&rule->ldap_storage_creds_list, &lsc->link); return lsc; } #endif od_rule_storage_t *od_rules_storage_add(od_rules_t *rules, od_rule_storage_t *storage) { od_list_append(&rules->storages, &storage->link); return storage; } od_rule_storage_t *od_rules_storage_match(od_rules_t *rules, const char *name) { od_list_t *i; od_list_foreach (&rules->storages, i) { od_rule_storage_t *storage; storage = od_container_of(i, od_rule_storage_t, link); if (strcmp(storage->name, name) == 0) { return storage; } } return NULL; } od_retcode_t od_rules_storages_watchdogs_run(od_logger_t *logger, od_rules_t *rules) { od_list_t *i; od_list_foreach (&rules->storages, i) { od_rule_storage_t *storage; storage = od_container_of(i, od_rule_storage_t, link); if (storage->watchdog && storage->watchdog_coro_id == -1) { int64_t coroutine_id; /* the ref is held by watchdog */ od_rules_storage_ref(storage); coroutine_id = machine_coroutine_create( od_storage_watchdog_watch, storage->watchdog); if (coroutine_id == INVALID_COROUTINE_ID) { od_rules_storage_unref(storage); od_error(logger, "system", NULL, NULL, "failed to start watchdog coroutine"); return NOT_OK_RESPONSE; } storage->watchdog_coro_id = coroutine_id; } } return OK_RESPONSE; } od_rule_auth_t *od_rules_auth_add(od_rule_t *rule) { od_rule_auth_t *auth = (od_rule_auth_t *)od_malloc(sizeof(od_rule_auth_t)); if (auth == NULL) { return NULL; } memset(auth, 0, sizeof(*auth)); od_list_init(&auth->link); od_list_append(&rule->auth_common_names, &auth->link); rule->auth_common_names_count++; return auth; } void od_rules_auth_free(od_rule_auth_t *auth) { if (auth->common_name) { od_free(auth->common_name); } od_free(auth); } static inline od_rule_auth_t *od_rules_auth_find(od_rule_t *rule, char *name) { od_list_t *i; od_list_foreach (&rule->auth_common_names, i) { od_rule_auth_t *auth; auth = od_container_of(i, od_rule_auth_t, link); if (!strcasecmp(auth->common_name, name)) { return auth; } } return NULL; } od_group_t *od_rules_group_allocate(od_global_t *global) { /* Allocate and force defaults */ od_group_t *group; group = od_malloc(sizeof(*group)); if (group == NULL) { return NULL; } memset(group, 0, sizeof(*group)); group->global = global; group->check_retry = 10; group->online = 0; od_list_init(&group->link); return group; } enum { GROUP_QUERY_FAIL, GROUP_QUERY_RFQ, GROUP_QUERY_CONTINUE, }; static int handle_group_query_msg(od_client_t *client, od_server_t *server, machine_msg_t *msg, od_list_t *members) { int rc; char *group_member; od_group_member_name_item_t *member = NULL; kiwi_be_type_t type; type = *(char *)machine_msg_data(msg); od_gdebug("group_checker", client, server, "%s", kiwi_be_type_to_string(type)); switch (type) { case KIWI_BE_ERROR_RESPONSE: od_backend_error(server, "group_checker", machine_msg_data(msg), machine_msg_size(msg)); return GROUP_QUERY_FAIL; case KIWI_BE_DATA_ROW: rc = od_group_parse_val_datarow(msg, &group_member); if (rc != OK_RESPONSE) { od_gerror("group_checker", client, server, "can't read data row"); return GROUP_QUERY_FAIL; } member = od_group_member_name_item_add(members); if (member == NULL) { od_free(group_member); return GROUP_QUERY_FAIL; } member->value = group_member; return GROUP_QUERY_CONTINUE; case KIWI_BE_READY_FOR_QUERY: od_backend_ready(server, machine_msg_data(msg), machine_msg_size(msg)); return GROUP_QUERY_RFQ; default: return GROUP_QUERY_CONTINUE; } } static int read_group_list(od_client_t *client, od_server_t *server, od_list_t *members, uint32_t timeout_ms) { while (1) { machine_msg_t *msg = od_read(&server->io, timeout_ms, OD_READ_BE); if (msg == NULL) { int err = mm_errno_get(); od_gerror("group_checker", client, server, "can't read response: %d (%s)", err, strerror(err)); return NOT_OK_RESPONSE; } int rc = handle_group_query_msg(client, server, msg, members); machine_msg_free(msg); if (rc == GROUP_QUERY_FAIL) { return NOT_OK_RESPONSE; } else if (rc == GROUP_QUERY_RFQ) { break; } } return OK_RESPONSE; } static void do_group_check_poll_connected(od_instance_t *instance, od_rule_t *rule, od_group_t *group, od_router_t *router, od_client_t *client) { od_list_t *i, *s; od_server_t *server = client->server; od_assert(server != NULL); int rc = od_backend_query_send(server, "group_checker", group->group_query, NULL, strlen(group->group_query) + 1, 1000 /* 1 sec */); if (rc != OK_RESPONSE) { /* retry later */ od_gerror("group_checker", client, server, "group query failed, errno=%d (%s)", mm_errno_get(), strerror(mm_errno_get())); return; } od_list_t members; od_list_init(&members); rc = read_group_list(client, server, &members, 1000); if (rc != OK_RESPONSE) { goto error; } size_t count = od_list_count(&members); char **usernames = od_malloc(sizeof(char *) * count); if (usernames == NULL) { goto error; } int j = 0; od_list_foreach (&members, i) { od_group_member_name_item_t *member; member = od_container_of(i, od_group_member_name_item_t, link); usernames[j++] = member->value; member->value = NULL; } if (instance->config.log_debug || rule->log_debug) { char buff[1024] = { 0 }; char *out = buff; char *end = buff + sizeof(buff); size_t k = 0; while (k < count && out < end) { out += snprintf(out, end - out, "'%s', ", usernames[k++]); } od_debug(&instance->logger, "group_checker", client, server, "%lu, [%s]", count, buff); } char **t_names; int t_count; od_router_lock(router); t_names = rule->user_names; t_count = rule->users_in_group; rule->user_names = usernames; rule->users_in_group = count; od_router_unlock(router); /* Free memory without router lock */ for (int i = 0; i < t_count; i++) { od_free(t_names[i]); } od_free(t_names); od_list_foreach_safe (&members, i, s) { od_group_member_name_item_t *member; member = od_container_of(i, od_group_member_name_item_t, link); od_list_unlink(&member->link); od_free(member->value); od_free(member); } return; error: od_list_foreach_safe (&members, i, s) { od_group_member_name_item_t *m; m = od_container_of(i, od_group_member_name_item_t, link); od_list_unlink(&m->link); od_free(m->value); od_free(m); } return; } static void do_group_check_iteration(od_instance_t *instance, od_rule_t *rule, od_group_t *group, od_router_t *router, od_client_t *client) { int rc; rc = od_attach_extended(instance, "group_checker", router, client); if (rc != OK_RESPONSE) { /* do nothing with error, go to retry */ return; } do_group_check_poll_connected(instance, rule, group, router, client); /* * yes, always close the connection * in fact, there is no need for that so * TODO: maybe just detach, not close the backend */ od_router_close(router, client); } static void do_group_check(void *arg) { od_rule_t *group_rule = arg; od_group_t *group = group_rule->group; od_global_t *global = group->global; od_router_t *router = global->router; od_instance_t *instance = global->instance; group->online = 1; od_log(&instance->logger, "group_checker", NULL, NULL, "start group checking for %s.%s", group_rule->db_name, group_rule->user_name); /* create internal auth client */ od_client_t *group_checker_client; group_checker_client = od_client_allocate_internal(global, "rule-group-checker"); if (group_checker_client == NULL) { od_error(&instance->logger, "group_checker", NULL, NULL, "route rule group_checker failed to allocate client"); goto to_return; } group_checker_client->global = global; group_checker_client->type = OD_POOL_CLIENT_INTERNAL; od_id_generate(&group_checker_client->id, "a"); /* set storage user and database */ kiwi_var_set(&group_checker_client->startup.user, KIWI_VAR_UNDEF, group->group_query_user, strlen(group->group_query_user) + 1); kiwi_var_set(&group_checker_client->startup.database, KIWI_VAR_UNDEF, group->group_query_db, strlen(group->group_query_db) + 1); int rc; rc = OK_RESPONSE; /* route */ od_router_status_t status; status = od_router_route(router, group_checker_client); od_debug(&instance->logger, "group_checker", group_checker_client, NULL, "routing to internal group_checker route status: %s", od_router_status_to_str(status)); if (status != OD_ROUTER_OK) { od_error(&instance->logger, "group_checker", group_checker_client, NULL, "route rule group_checker failed: %s", od_router_status_to_str(status)); goto to_return; } while (group->online) { rc = machine_wait_flag_wait( group_rule->group_checker_online, instance->config.group_checker_interval); if (rc == 0) { od_log(&instance->logger, "group_checker", NULL, NULL, "done flag is set, exiting from rule group checker for %s.%s", group_rule->db_name, group_rule->user_name); break; } if (rc == -1 && machine_errno() != ETIMEDOUT) { od_error(&instance->logger, "group_checker", NULL, NULL, "can't wait done flag: %s (%d)", strerror(machine_errno()), machine_errno()); break; } /* do not check return status, just return */ do_group_check_iteration(instance, group_rule, group, router, group_checker_client); } od_router_unroute(router, group_checker_client); od_client_free_extended(group_checker_client); od_group_free(group); char **t_names; int t_count; od_router_lock(router); t_names = group_rule->user_names; t_count = group_rule->users_in_group; group_rule->user_names = NULL; group_rule->users_in_group = 0; od_router_unlock(router); for (int i = 0; i < t_count; i++) { od_free(t_names[i]); } od_free(t_names); to_return: od_log(&instance->logger, "group_checker", NULL, NULL, "group checking for %s.%s finished", group_rule->db_name, group_rule->user_name); machine_wait_flag_set(group_rule->group_checker_finished); od_rules_unref(group_rule); } od_retcode_t od_rules_groups_checkers_run(od_logger_t *logger, od_rules_t *rules) { od_list_t *i; od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); if (rule->group && !rule->obsolete && !rule->group->online) { /* group checkers holds the ref - it will do unref on exit */ od_rules_ref(rule); rule->group_checker_machine_id = machine_coroutine_create(do_group_check, rule); if (rule->group_checker_machine_id == INVALID_COROUTINE_ID) { od_rules_unref(rule); od_error( logger, "system", NULL, NULL, "failed to start group_checker coroutine"); return NOT_OK_RESPONSE; } } } return OK_RESPONSE; } static od_rule_t *od_rules_add(od_rules_t *rules) { machine_wait_flag_t *df = machine_wait_flag_create(); if (df == NULL) { return NULL; } machine_wait_flag_t *gcf = machine_wait_flag_create(); if (gcf == NULL) { machine_wait_flag_destroy(df); return NULL; } od_rule_t *rule; rule = (od_rule_t *)od_malloc(sizeof(od_rule_t)); if (rule == NULL) { machine_wait_flag_destroy(df); machine_wait_flag_destroy(gcf); return NULL; } memset(rule, 0, sizeof(*rule)); rule->group_checker_machine_id = -1; /* pool */ rule->pool = od_rule_pool_alloc(); if (rule->pool == NULL) { machine_wait_flag_destroy(df); machine_wait_flag_destroy(gcf); od_free(rule); return NULL; } rule->group_checker_online = df; rule->group_checker_finished = gcf; rule->user_role = OD_RULE_ROLE_UNDEF; /* backward compatibility */ rule->maintain_params = 1; rule->obsolete = 0; rule->mark = 0; atomic_store(&rule->refs, 1); rule->order = rules->next_order++; rule->conn_type = OD_RULE_CONN_TYPE_DEFAULT; rule->target_session_attrs = OD_TARGET_SESSION_ATTRS_UNDEF; rule->auth_common_name_default = 0; rule->auth_common_names_count = 0; rule->server_lifetime_us = 3600 * 1000000L; rule->server_drop_on_cached_plan_error = 1; rule->server_drop_on_oom = 1; rule->client_show_id = 1; rule->reserve_session_server_connection = 1; #ifdef PAM_FOUND rule->auth_pam_data = od_pam_auth_data_create(); #endif #ifdef LDAP_FOUND rule->ldap_endpoint_name = NULL; rule->ldap_endpoint = NULL; rule->ldap_storage_credentials_attr = NULL; od_list_init(&rule->ldap_storage_creds_list); #endif kiwi_vars_init(&rule->vars); rule->enable_password_passthrough = 0; od_list_init(&rule->auth_common_names); od_list_init(&rule->link); od_list_append(&rules->rules, &rule->link); rule->quantiles = NULL; return rule; } od_rule_t *od_rules_add_new_rule(od_rules_t *rules, const char *dbname, int db_is_default, const char *user, int user_is_default, const od_address_range_t *address_range, od_rule_conn_type_t conn_type, int pool_internal) { od_rule_t *rule = NULL; rule = od_rules_match(rules, dbname, user, address_range, conn_type, db_is_default, user_is_default, pool_internal); if (rule != NULL) { /* already defined */ return NULL; } rule = od_rules_add(rules); if (rule == NULL) { return NULL; } rule->db_is_default = db_is_default; rule->db_name_len = strlen(dbname); rule->db_name = od_strdup(dbname); if (rule->db_name == NULL) { goto error; } rule->user_is_default = user_is_default; rule->user_name_len = strlen(user); rule->user_name = od_strdup(user); if (rule->user_name == NULL) { goto error; } if (od_address_range_copy(address_range, &rule->address_range)) { goto error; } rule->conn_type = conn_type; return rule; error: od_rules_rule_free(rule); return NULL; } void od_rules_rule_free(od_rule_t *rule) { od_rules_unref(rule); } static void od_rules_rule_free_now(od_rule_t *rule) { if (rule->password) { od_free(rule->password); } if (rule->auth) { od_free(rule->auth); } if (rule->auth_query) { od_free(rule->auth_query); } if (rule->auth_query_db) { od_free(rule->auth_query_db); } if (rule->auth_query_user) { od_free(rule->auth_query_user); } if (rule->storage) { od_rules_storage_unref(rule->storage); } if (rule->storage_name) { od_free(rule->storage_name); } if (rule->storage_db) { od_free(rule->storage_db); } if (rule->storage_user) { od_free(rule->storage_user); } if (rule->storage_password) { od_free(rule->storage_password); } if (rule->pool) { od_rule_pool_free(rule->pool); } if (rule->mdb_iamproxy_socket_path) { od_free(rule->mdb_iamproxy_socket_path); } if (rule->shared_pool) { od_shared_pool_unref(rule->shared_pool); } if (rule->backend_startup_vars) { for (size_t j = 0; j < rule->backend_startup_vars_sz; j++) { od_free(rule->backend_startup_vars[j].name); od_free(rule->backend_startup_vars[j].value); } od_free(rule->backend_startup_vars); } od_list_t *i, *n; od_list_foreach_safe (&rule->auth_common_names, i, n) { od_rule_auth_t *auth; auth = od_container_of(i, od_rule_auth_t, link); od_rules_auth_free(auth); } #ifdef PAM_FOUND od_pam_auth_data_free(rule->auth_pam_data); #endif #ifdef LDAP_FOUND if (rule->ldap_endpoint_name) { od_free(rule->ldap_endpoint_name); } if (rule->ldap_storage_credentials_attr) { od_free(rule->ldap_storage_credentials_attr); } if (rule->ldap_endpoint) { od_ldap_endpoint_free(rule->ldap_endpoint); } if (!od_list_empty(&rule->ldap_storage_creds_list)) { od_list_foreach_safe (&rule->ldap_storage_creds_list, i, n) { od_ldap_storage_credentials_t *lsc; lsc = od_container_of(i, od_ldap_storage_credentials_t, link); od_ldap_storage_credentials_free(lsc); } } #endif if (rule->auth_module) { od_free(rule->auth_module); } if (rule->quantiles) { od_free(rule->quantiles); } /* * group checker uses db_name and user_name * so free them at a last */ if (rule->db_name) { od_free(rule->db_name); } if (rule->user_name) { od_free(rule->user_name); } if (rule->address_range.string_value) { od_free(rule->address_range.string_value); } machine_wait_flag_destroy(rule->group_checker_online); machine_wait_flag_destroy(rule->group_checker_finished); od_list_unlink(&rule->link); od_free(rule); } void od_rules_ref(od_rule_t *rule) { atomic_fetch_add(&rule->refs, 1); } void od_rules_unref(od_rule_t *rule) { int64_t r = atomic_fetch_sub(&rule->refs, 1); od_assert(r >= 1); if (r == 1) { od_rules_rule_free_now(rule); } } static int od_rules_rule_get_specificity(const od_rule_t *rule) { /* * from specified db, user, address, connection type to default values * smth like: * - db.user * - db.default * - default.user * - default.default * * this means that db < user < address < connection type * let higher priority mean earlier comparison when matching */ int specificity = 0; if (!rule->db_is_default) { specificity += 1000; } if (!rule->user_is_default) { specificity += 100; } if (!rule->address_range.is_default) { specificity += 10; } if (rule->conn_type != OD_RULE_CONN_TYPE_DEFAULT) { specificity += 1; } return specificity; } static int od_rules_rule_db_cmp(const od_rule_t *a, const od_rule_t *b) { if (a->db_is_default) { if (b->db_is_default) { return 0; } return 1; } if (b->db_is_default) { return -1; } return strcmp(a->db_name, b->db_name); } static int od_rules_rule_user_cmp(const od_rule_t *a, const od_rule_t *b) { if (a->user_is_default) { if (b->user_is_default) { return 0; } return 1; } if (b->user_is_default) { return -1; } return strcmp(a->user_name, b->user_name); } static int od_rules_rule_address_cmp(const od_rule_t *a, const od_rule_t *b) { const od_address_range_t *ar_a = &a->address_range; const od_address_range_t *ar_b = &b->address_range; if (ar_a->is_default) { if (ar_b->is_default) { return 0; } return 1; } if (ar_b->is_default) { return -1; } if (ar_a->string_value_len > ar_b->string_value_len) { int cmp = strncmp(ar_a->string_value, ar_b->string_value, ar_b->string_value_len); if (cmp != 0) { return cmp; } /* same prefix, but bigger length - a is greater */ return 1; } if (ar_a->string_value_len < ar_b->string_value_len) { int cmp = strncmp(ar_a->string_value, ar_b->string_value, ar_a->string_value_len); if (cmp != 0) { return cmp; } /* same prefix, but smaller length - a is less */ return -1; } return strncmp(ar_a->string_value, ar_b->string_value, ar_a->string_value_len); } static int od_rules_rule_conn_type_cmp(const od_rule_t *a, const od_rule_t *b) { return (int)b->conn_type - (int)a->conn_type; } static int od_rules_rule_cmp(const od_rule_t *a, const od_rule_t *b) { int cmp = od_rules_rule_db_cmp(a, b); if (cmp != 0) { return cmp; } cmp = od_rules_rule_user_cmp(a, b); if (cmp != 0) { return cmp; } cmp = od_rules_rule_address_cmp(a, b); if (cmp != 0) { return cmp; } return od_rules_rule_conn_type_cmp(a, b); } static int od_rules_rule_specificity_cmp(const void *a, const void *b) { const od_rule_t *rule_a = *((const od_rule_t **)a); const od_rule_t *rule_b = *((const od_rule_t **)b); int specificity_a = od_rules_rule_get_specificity(rule_a); int specificity_b = od_rules_rule_get_specificity(rule_b); /* let obsolete rules stay in the end of list */ if (rule_a->obsolete) { specificity_a = -1; } if (rule_b->obsolete) { specificity_b = -1; } /* from higher specificity to lower, so invert comparison */ if (specificity_a > specificity_b) { return -1; } if (specificity_a < specificity_b) { return 1; } return od_rules_rule_cmp(rule_a, rule_b); } static int od_rules_rule_order_cmp(const void *a, const void *b) { const od_rule_t *rule_a = *((const od_rule_t **)a); const od_rule_t *rule_b = *((const od_rule_t **)b); /* let obsolete rules stay in the end of list */ int order_a = rule_a->obsolete ? INT_MAX : rule_a->order; int order_b = rule_b->obsolete ? INT_MAX : rule_b->order; if (order_a < order_b) { return -1; } if (order_a > order_b) { return 1; } return od_rules_rule_cmp(rule_a, rule_b); } void od_rules_stop_checkers(od_rules_t *rules) { od_list_t *n, *i; od_list_foreach_safe (&rules->rules, i, n) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); if (rule->group_checker_machine_id != -1) { machine_wait_flag_set(rule->group_checker_online); machine_wait_flag_wait(rule->group_checker_finished, UINT32_MAX); rule->group_checker_machine_id = -1; } } } int od_rules_sort_for_matching(od_rules_t *rules) { size_t count = od_list_count(&rules->rules); if (count == 0) { return 0; } od_rule_t **sorted = od_malloc(count * sizeof(od_rule_t *)); if (sorted == NULL) { return 1; } size_t index = 0; od_list_t *i, *n; od_list_foreach_safe (&rules->rules, i, n) { od_rule_t *rule = od_container_of(i, od_rule_t, link); od_list_unlink(&rule->link); sorted[index++] = rule; } /* * is we are matching rules sequentialy (in order they written in config) * then we must sort them by order * * in case when maching is not sequential, then we must * place default-containing rules at the end * (default routes has lower priority when matching) */ int sequential = od_global_get_instance()->config.sequential_routing; if (sequential) { qsort(sorted, count, sizeof(od_rule_t *), od_rules_rule_order_cmp); } else { qsort(sorted, count, sizeof(od_rule_t *), od_rules_rule_specificity_cmp); } for (index = 0; index < count; ++index) { od_list_append(&rules->rules, &sorted[index]->link); } od_free(sorted); return 0; } static int od_rule_db_match(const od_rule_t *rule, const char *dbname) { if (rule->db_is_default) { return 1; } return strcmp(rule->db_name, dbname) == 0; } static int od_rule_user_match(const od_rule_t *rule, const char *user) { if (rule->user_is_default) { return 1; } return od_name_in_rule(rule, user); } static int od_rule_address_match(const od_rule_t *rule, struct sockaddr_storage *uaddr) { if (rule->address_range.is_default) { return 1; } return od_address_validate(&rule->address_range, uaddr); } static int od_rule_conn_type_match(const od_rule_t *rule, const kiwi_be_startup_t *startup, struct sockaddr_storage *user_addr) { if (rule->conn_type == OD_RULE_CONN_TYPE_DEFAULT) { return 1; } sa_family_t family = user_addr->ss_family; int is_ssl = startup->is_ssl_request; int is_host = family == AF_INET || family == AF_INET6; int is_local = family == AF_UNIX; switch (rule->conn_type) { case OD_RULE_CONN_TYPE_LOCAL: return is_local; case OD_RULE_CONN_TYPE_HOST: return is_host; case OD_RULE_CONN_TYPE_HOSTSSL: return is_host && is_ssl; case OD_RULE_CONN_TYPE_HOSTNOSSL: return is_host && !is_ssl; case OD_RULE_CONN_TYPE_DEFAULT: default: abort(); } } static od_rule_t *od_rules_find_first_matching( od_rules_t *rules, const kiwi_be_startup_t *startup, struct sockaddr_storage *user_addr, int pool_internal) { const char *dbname = startup->database.value; const char *user = startup->user.value; /* * Here we can find first matching, because of rules sorting * in case sequential routing - the rules are sorted by order * in case of non-sequential routing - the rules are sorted by specificity * (specificity means a measure of how well the user fits the * rule exactly, not just by 'default' comparison) */ od_list_t *i; od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); if (rule->obsolete) { continue; } if (pool_internal) { if (rule->pool->routing != OD_RULE_POOL_INTERNAL) { continue; } } else { if (rule->pool->routing != OD_RULE_POOL_CLIENT_VISIBLE) { continue; } } if (!od_rule_db_match(rule, dbname)) { continue; } if (!od_rule_user_match(rule, user)) { continue; } if (!od_rule_address_match(rule, user_addr)) { continue; } if (!od_rule_conn_type_match(rule, startup, user_addr)) { continue; } return rule; } return NULL; } od_rule_t *od_rules_forward(od_rules_t *rules, const kiwi_be_startup_t *startup, struct sockaddr_storage *user_addr, int pool_internal) { return od_rules_find_first_matching(rules, startup, user_addr, pool_internal); } static inline int od_rule_match(od_rule_t *rule, const char *dbname, const char *user, const od_address_range_t *address_range, od_rule_conn_type_t conn_type, int db_is_default, int user_is_default) { if (strcmp(rule->db_name, dbname) != 0) { return 0; } if (!od_name_in_rule(rule, user)) { return 0; } if (rule->conn_type != conn_type) { return 0; } if (rule->address_range.is_default != address_range->is_default) { return 0; } if (rule->db_is_default != db_is_default) { return 0; } if (rule->user_is_default != user_is_default) { return 0; } if (!address_range->is_default) { return od_address_range_equals(&rule->address_range, address_range); } return 1; } od_rule_t *od_rules_match(od_rules_t *rules, const char *db_name, const char *user_name, const od_address_range_t *address_range, od_rule_conn_type_t conn_type, int db_is_default, int user_is_default, int pool_internal) { od_list_t *i; od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); /* filter out internal or client-vidible rules */ if (pool_internal) { if (rule->pool->routing != OD_RULE_POOL_INTERNAL) { continue; } } else { if (rule->pool->routing != OD_RULE_POOL_CLIENT_VISIBLE) { continue; } } if (od_rule_match(rule, db_name, user_name, address_range, conn_type, db_is_default, user_is_default)) { return rule; } } return NULL; } static inline od_rule_t * od_rules_match_active(od_rules_t *rules, char *db_name, char *user_name, od_address_range_t *address_range, od_rule_conn_type_t conn_type) { od_list_t *i; od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); if (rule->obsolete) { continue; } if (strcmp(rule->db_name, db_name) == 0 && od_name_in_rule(rule, user_name) && od_address_range_equals(&rule->address_range, address_range) && rule->conn_type == conn_type) { return rule; } } return NULL; } int od_rules_storage_compare(od_rule_storage_t *a, od_rule_storage_t *b) { if (a == NULL || b == NULL) { return a == b; } /* type */ if (a->storage_type != b->storage_type) { return 0; } /* type */ if (a->server_max_routing != b->server_max_routing) { return 0; } /* host */ if (a->host && b->host) { if (strcmp(a->host, b->host) != 0) { return 0; } } else if (a->host || b->host) { return 0; } /* port */ if (a->port != b->port) { return 0; } /* tls_opts->tls_mode */ if (a->tls_opts->tls_mode != b->tls_opts->tls_mode) { return 0; } /* tls_opts->tls_ca_file */ if (a->tls_opts->tls_ca_file && b->tls_opts->tls_ca_file) { if (strcmp(a->tls_opts->tls_ca_file, b->tls_opts->tls_ca_file) != 0) { return 0; } } else if (a->tls_opts->tls_ca_file || b->tls_opts->tls_ca_file) { return 0; } /* tls_opts->tls_key_file */ if (a->tls_opts->tls_key_file && b->tls_opts->tls_key_file) { if (strcmp(a->tls_opts->tls_key_file, b->tls_opts->tls_key_file) != 0) { return 0; } } else if (a->tls_opts->tls_key_file || b->tls_opts->tls_key_file) { return 0; } /* tls_opts->tls_cert_file */ if (a->tls_opts->tls_cert_file && b->tls_opts->tls_cert_file) { if (strcmp(a->tls_opts->tls_cert_file, b->tls_opts->tls_cert_file) != 0) { return 0; } } else if (a->tls_opts->tls_cert_file || b->tls_opts->tls_cert_file) { return 0; } /* tls_opts->tls_protocols */ if (a->tls_opts->tls_protocols && b->tls_opts->tls_protocols) { if (strcmp(a->tls_opts->tls_protocols, b->tls_opts->tls_protocols) != 0) { return 0; } } else if (a->tls_opts->tls_protocols || b->tls_opts->tls_protocols) { return 0; } /* endpoints_status_poll_interval_ms */ if (a->endpoints_status_poll_interval_ms != b->endpoints_status_poll_interval_ms) { return 0; } /* endpoints count */ if (a->endpoints_count != b->endpoints_count) { return 0; } /* endpoints addresses */ for (size_t k = 0; k < a->endpoints_count; k++) { if (od_address_cmp(&a->endpoints[k].address, &b->endpoints[k].address) != 0) { return 0; } } /* balancing */ if (a->balancing.method.type != b->balancing.method.type) { return 0; } if (a->balancing.method.az_aware != b->balancing.method.az_aware) { return 0; } if (a->balancing.debug_notice != b->balancing.debug_notice) { return 0; } /* balancing: weighted */ if (a->balancing.method.weighted.nweights != b->balancing.method.weighted.nweights) { return 0; } for (size_t k = 0; k < a->balancing.method.weighted.nweights; k++) { od_balancing_host_weight_t *aw = &a->balancing.method.weighted.weights[k]; od_balancing_host_weight_t *bw = &b->balancing.method.weighted.weights[k]; if (aw->weight != bw->weight) { return 0; } if ((aw->host == NULL) != (bw->host == NULL)) { return 0; } if (aw->host && bw->host && strcmp(aw->host, bw->host) != 0) { return 0; } } /* balancing: weighted_leastconn */ if (a->balancing.method.weighted_leastconn.nweights != b->balancing.method.weighted_leastconn.nweights) { return 0; } for (size_t k = 0; k < a->balancing.method.weighted_leastconn.nweights; k++) { od_balancing_host_weight_t *aw = &a->balancing.method.weighted_leastconn.weights[k]; od_balancing_host_weight_t *bw = &b->balancing.method.weighted_leastconn.weights[k]; if (aw->weight != bw->weight) { return 0; } if ((aw->host == NULL) != (bw->host == NULL)) { return 0; } if (aw->host && bw->host && strcmp(aw->host, bw->host) != 0) { return 0; } } /* balancing: responsetime */ if (a->balancing.method.responsetime.time_weight != b->balancing.method.responsetime.time_weight) { return 0; } if (a->balancing.method.responsetime.conn_weight != b->balancing.method.responsetime.conn_weight) { return 0; } return 1; } int od_rules_rule_compare(od_rule_t *a, od_rule_t *b) { /* db default */ if (a->db_is_default != b->db_is_default) { return 0; } /* user default */ if (a->user_is_default != b->user_is_default) { return 0; } if (a->conn_type != b->conn_type) { return 0; } /* password */ if (a->password && b->password) { if (strcmp(a->password, b->password) != 0) { return 0; } } else if (a->password || b->password) { return 0; } /* role */ if (a->user_role != b->user_role) { return 0; } /* quantiles changed */ if (a->quantiles_count == b->quantiles_count) { if (a->quantiles_count != 0 && memcmp(a->quantiles, b->quantiles, sizeof(double) * a->quantiles_count) != 0) { return 0; } } else { return 0; } /* auth */ if (a->auth_mode != b->auth_mode) { return 0; } /* auth query */ if (a->auth_query && b->auth_query) { if (strcmp(a->auth_query, b->auth_query) != 0) { return 0; } } else if (a->auth_query || b->auth_query) { return 0; } /* auth query db */ if (a->auth_query_db && b->auth_query_db) { if (strcmp(a->auth_query_db, b->auth_query_db) != 0) { return 0; } } else if (a->auth_query_db || b->auth_query_db) { return 0; } /* auth query user */ if (a->auth_query_user && b->auth_query_user) { if (strcmp(a->auth_query_user, b->auth_query_user) != 0) { return 0; } } else if (a->auth_query_user || b->auth_query_user) { return 0; } /* auth common name default */ if (a->auth_common_name_default != b->auth_common_name_default) { return 0; } /* auth common names count */ if (a->auth_common_names_count != b->auth_common_names_count) { return 0; } /* compare auth common names */ od_list_t *i; od_list_foreach (&a->auth_common_names, i) { od_rule_auth_t *auth; auth = od_container_of(i, od_rule_auth_t, link); if (!od_rules_auth_find(b, auth->common_name)) { return 0; } } /* storage */ if (strcmp(a->storage_name, b->storage_name) != 0) { return 0; } if (!od_rules_storage_compare(a->storage, b->storage)) { return 0; } /* storage_db */ if (a->storage_db && b->storage_db) { if (strcmp(a->storage_db, b->storage_db) != 0) { return 0; } } else if (a->storage_db || b->storage_db) { return 0; } /* storage_user */ if (a->storage_user && b->storage_user) { if (strcmp(a->storage_user, b->storage_user) != 0) { return 0; } } else if (a->storage_user || b->storage_user) { return 0; } /* storage_password */ if (a->storage_password && b->storage_password) { if (strcmp(a->storage_password, b->storage_password) != 0) { return 0; } } else if (a->storage_password || b->storage_password) { return 0; } /* pool */ if (!od_rule_pool_compare(a->pool, b->pool)) { return 0; } /* client_fwd_error */ if (a->client_fwd_error != b->client_fwd_error) { return 0; } if (a->client_show_id != b->client_show_id) { return 0; } /* reserve_session_server_connection */ if (a->reserve_session_server_connection != b->reserve_session_server_connection) { return 0; } if (a->catchup_timeout != b->catchup_timeout) { return 0; } if (a->catchup_checks != b->catchup_checks) { return 0; } /* client_max */ if (a->client_max != b->client_max) { return 0; } /* server_lifetime */ if (a->server_lifetime_us != b->server_lifetime_us) { return 0; } if (a->server_drop_on_oom != b->server_drop_on_oom) { return 0; } /* maintain_params */ if (a->maintain_params != b->maintain_params) { return 0; } /* keepalive */ if (a->keepalive != b->keepalive) { return 0; } /* keepalive_keep_interval */ if (a->keepalive_keep_interval != b->keepalive_keep_interval) { return 0; } /* keepalive_probes */ if (a->keepalive_probes != b->keepalive_probes) { return 0; } /* keepalive_usr_timeout */ if (a->keepalive_usr_timeout != b->keepalive_usr_timeout) { return 0; } return 1; } int od_rules_rule_compare_to_drop(od_rule_t *a, od_rule_t *b) { /* role */ if (a->user_role < b->user_role) { return 0; } /* force drop rules with shared pools */ if (a->shared_pool != NULL || b->shared_pool != NULL) { return 0; } return 1; } static od_rule_key_t *rk_of(od_rule_t *rule) { od_rule_key_t *rk = od_malloc(sizeof(od_rule_key_t)); if (rk == NULL) { /* TODO: better */ abort(); } od_rule_key_init(rk); rk->usr_name = od_strndup(rule->user_name, rule->user_name_len); rk->db_name = od_strndup(rule->db_name, rule->db_name_len); od_address_range_copy(&rule->address_range, &rk->address_range); rk->conn_type = rule->conn_type; return rk; } int od_rules_merge(od_rules_t *rules, od_rules_t *src, od_list_t *added, od_list_t *deleted, od_list_t *to_drop, od_list_t *not_changed) { int count_mark = 0; int count_deleted = 0; int count_new = 0; od_list_t *i; /* mark all rules for obsoletion */ od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); rule->mark = 1; count_mark++; } /* select dropped rules */ od_list_t *n; od_list_foreach_safe (&rules->rules, i, n) { od_rule_t *rule_old; rule_old = od_container_of(i, od_rule_t, link); int ok = 0; od_list_t *m; od_list_t *j; od_list_foreach_safe (&src->rules, j, m) { od_rule_t *rule_new; rule_new = od_container_of(j, od_rule_t, link); if (strcmp(rule_old->user_name, rule_new->user_name) == 0 && strcmp(rule_old->db_name, rule_new->db_name) == 0 && od_address_range_equals(&rule_old->address_range, &rule_new->address_range) && rule_old->conn_type == rule_new->conn_type) { ok = 1; break; } } if (!ok) { od_rule_key_t *rk = rk_of(rule_old); od_list_append(deleted, &rk->link); ++count_deleted; } }; /* select added rules */ od_list_foreach_safe (&src->rules, i, n) { od_rule_t *rule_new; rule_new = od_container_of(i, od_rule_t, link); int ok = 0; od_list_t *m; od_list_t *j; od_list_foreach_safe (&rules->rules, j, m) { od_rule_t *rule_old; rule_old = od_container_of(j, od_rule_t, link); if (strcmp(rule_old->user_name, rule_new->user_name) == 0 && strcmp(rule_old->db_name, rule_new->db_name) == 0 && od_address_range_equals(&rule_old->address_range, &rule_new->address_range) && rule_old->conn_type == rule_new->conn_type) { ok = 1; break; } } if (!ok) { od_rule_key_t *rk = rk_of(rule_new); od_list_append(added, &rk->link); } } /* select new rules */ od_list_foreach_safe (&src->rules, i, n) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); /* find and compare origin rule */ od_rule_t *origin; origin = od_rules_match_active(rules, rule->db_name, rule->user_name, &rule->address_range, rule->conn_type); if (origin) { /* * If the rule is unchanged (same settings, same * storage), keep the origin alive and drop the * freshly parsed copy. Since od_rules_storage_merge * already re-pointed rule->storage to the origin's * storage (when the storage is unchanged), the * pointer comparison inside od_rules_rule_compare * will succeed and the origin keeps its backend * connections, watchdog and endpoint statuses. * * However, default rules (db_is_default or * user_is_default) are always force-dropped: a new * dedicated rule may shadow them, and existing * routes matched via the default rule must be * recreated to pick up the dedicated rule. */ if (origin->shared_pool == NULL && rule->shared_pool == NULL && !origin->db_is_default && !origin->user_is_default && od_rules_rule_compare(origin, rule)) { origin->mark = 0; count_mark--; origin->order = rule->order; od_rule_key_t *rk = rk_of(origin); od_list_append(not_changed, &rk->link); continue; } if (!od_rules_rule_compare_to_drop(origin, rule)) { od_rule_key_t *rk = rk_of(origin); od_list_append(to_drop, &rk->link); } /* add new version, origin version still exists */ } else { /* add new version */ /* od_list_append(added, &rule->link); */ } od_list_unlink(&rule->link); od_list_init(&rule->link); od_list_append(&rules->rules, &rule->link); #ifdef PAM_FOUND if (rule->auth_pam_data) { od_pam_auth_data_free(rule->auth_pam_data); } rule->auth_pam_data = od_pam_auth_data_create(); #endif count_new++; } /* try to free obsolete schemes, which are unused by any * rule at the moment */ if (count_mark > 0) { od_list_foreach_safe (&rules->rules, i, n) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); int is_obsolete = rule->obsolete || rule->mark; rule->mark = 0; rule->obsolete = is_obsolete; if (is_obsolete) { od_list_unlink(&rule->link); od_list_init(&rule->link); od_rules_unref(rule); } } } /* * the rules list was changed, need to sort it for matching correct work */ od_rules_sort_for_matching(rules); return count_new + count_mark + count_deleted; } int od_pool_validate(od_logger_t *logger, od_rule_pool_t *pool, char *db_name, char *user_name, od_address_range_t *address_range) { /* pooling mode */ if (!pool->pool_type_str) { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': pooling mode is not set", db_name, user_name, address_range->string_value); return NOT_OK_RESPONSE; } if (strcmp(pool->pool_type_str, "session") == 0) { pool->pool_type = OD_RULE_POOL_SESSION; } else if (strcmp(pool->pool_type_str, "transaction") == 0) { pool->pool_type = OD_RULE_POOL_TRANSACTION; } else if (strcmp(pool->pool_type_str, "statement") == 0) { pool->pool_type = OD_RULE_POOL_STATEMENT; } else { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': unknown pooling mode", db_name, user_name, address_range->string_value); return NOT_OK_RESPONSE; } pool->routing = OD_RULE_POOL_CLIENT_VISIBLE; if (!pool->routing_type) { od_debug( logger, "rules", NULL, NULL, "rule '%s.%s %s': pool routing mode is not set, assuming \"client_visible\" by default", db_name, user_name, address_range->string_value); } else if (strcmp(pool->routing_type, "internal") == 0) { pool->routing = OD_RULE_POOL_INTERNAL; } else if (strcmp(pool->routing_type, "client_visible") == 0) { pool->routing = OD_RULE_POOL_CLIENT_VISIBLE; } else { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': unknown pool routing mode", db_name, user_name, address_range->string_value); return NOT_OK_RESPONSE; } if (pool->routing == OD_RULE_POOL_INTERNAL && !address_range->is_default) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': internal rules must have default address_range", db_name, user_name, address_range->string_value); return NOT_OK_RESPONSE; } if (pool->pool_type == OD_RULE_POOL_SESSION && pool->pin_on_listen) { od_error( logger, "rules", NULL, NULL, "setting pool_pin_on_listen with session pool for %s.%s makes no sense", db_name, user_name); return NOT_OK_RESPONSE; } /* reserve prepare statement feature */ if (pool->reserve_prepared_statement && pool->pool_type == OD_RULE_POOL_SESSION) { pool->reserve_prepared_statement = 0; od_log(logger, "rules", NULL, NULL, "rule '%s.%s %s': disable prepared statements reserving due to session pooling", db_name, user_name, address_range->string_value); } if (!pool->reserve_prepared_statement && pool->server_pstmt_cache_size > 0) { pool->server_pstmt_cache_size = 0; od_log(logger, "rules", NULL, NULL, "rule '%s.%s %s': ignore server_pstmt_cache_size due to disabled prepared statements reserving", db_name, user_name, address_range->string_value); } if (pool->reserve_prepared_statement && pool->discard) { pool->discard = 0; pool->smart_discard = 1; od_log(logger, "rules", NULL, NULL, "rule '%s.%s %s': replace pool_discard with pool_smart_discard due to prepared statements reserving", db_name, user_name, address_range->string_value); } if (pool->smart_discard && !pool->reserve_prepared_statement) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': pool smart discard is forbidden without using prepared statements support", db_name, user_name, address_range->string_value); return NOT_OK_RESPONSE; } if (pool->discard_query && pool->reserve_prepared_statement) { if (strcasestr(pool->discard_query, "DEALLOCATE ALL")) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': cannot support prepared statements when 'DEALLOCATE ALL' present in discard string", db_name, user_name, address_range->string_value); return NOT_OK_RESPONSE; } } return OK_RESPONSE; } int od_rules_autogenerate_defaults(od_rules_t *rules, od_logger_t *logger) { od_rule_t *rule; od_rule_t *default_rule; od_list_t *i; bool need_autogen = false; /* rules */ od_list_foreach (&rules->rules, i) { rule = od_container_of(i, od_rule_t, link); /* match storage and make a copy of in the user rules */ if (rule->auth_query != NULL && !od_rules_match(rules, rule->db_name, rule->user_name, &rule->address_range, rule->conn_type, rule->db_is_default, rule->user_is_default, 1)) { need_autogen = true; break; } } od_address_range_t default_address_range = od_address_range_create_default(); if (!need_autogen || od_rules_match(rules, "default_db", "default_user", &default_address_range, OD_RULE_CONN_TYPE_DEFAULT, 1, 1, 1)) { od_log(logger, "config", NULL, NULL, "skipping default internal rule auto-generation: no need in them"); od_address_range_destroy(&default_address_range); return OK_RESPONSE; } default_rule = od_rules_match(rules, "default_db", "default_user", &default_address_range, OD_RULE_CONN_TYPE_DEFAULT, 1, 1, 0); if (!default_rule) { od_log(logger, "config", NULL, NULL, "skipping default internal rule auto-generation: no default rule provided"); od_address_range_destroy(&default_address_range); return OK_RESPONSE; } if (!default_rule->storage) { od_log(logger, "config", NULL, NULL, "skipping default internal rule auto-generation: default rule storage not set"); od_address_range_destroy(&default_address_range); return OK_RESPONSE; } if (!default_rule->storage_password) { od_log(logger, "config", NULL, NULL, "skipping default internal rule auto-generation: default rule storage password not set"); od_address_range_destroy(&default_address_range); return OK_RESPONSE; } rule = od_rules_add(rules); if (rule == NULL) { od_address_range_destroy(&default_address_range); return NOT_OK_RESPONSE; } rule->user_is_default = 1; rule->user_name_len = sizeof("default_user"); /* we need malloc'd string here */ rule->user_name = od_strdup("default_user"); if (rule->user_name == NULL) { od_address_range_destroy(&default_address_range); return NOT_OK_RESPONSE; } rule->db_is_default = 1; rule->db_name_len = sizeof("default_db"); /* we need malloc'd string here */ rule->db_name = od_strdup("default_db"); if (rule->db_name == NULL) { od_address_range_destroy(&default_address_range); return NOT_OK_RESPONSE; } rule->address_range = default_address_range; rule->conn_type = OD_RULE_CONN_TYPE_DEFAULT; /* force several default settings */ #define OD_DEFAULT_INTERNAL_POLL_SZ 0 rule->pool->pool_type_str = od_strdup("transaction"); if (rule->pool->pool_type_str == NULL) { return NOT_OK_RESPONSE; } rule->pool->pool_type = OD_RULE_POOL_TRANSACTION; rule->pool->routing_type = od_strdup("internal"); if (rule->pool->routing_type == NULL) { return NOT_OK_RESPONSE; } rule->pool->routing = OD_RULE_POOL_INTERNAL; rule->pool->size = OD_DEFAULT_INTERNAL_POLL_SZ; rule->enable_password_passthrough = true; rule->storage = od_rules_storage_ref(default_rule->storage); rule->storage_password = od_strdup(default_rule->storage_password); if (rule->storage_password == NULL) { return NOT_OK_RESPONSE; } rule->storage_password_len = default_rule->storage_password_len; od_log(logger, "config", NULL, NULL, "default internal rule auto-generated"); return OK_RESPONSE; } static inline int od_rules_validate_endpoints(od_logger_t *logger, od_config_t *config, od_rule_storage_t *storage) { if (storage->host == NULL) { if (config->unix_socket_dir == NULL) { od_error(logger, "rules", NULL, NULL, "storage '%s': no host specified and " "unix_socket_dir is not set", storage->name); return -1; } /* enforce one endpoint with unix address */ char buff[1024]; od_snprintf(buff, sizeof(buff), "%s/.s.PGSQL.%d", config->unix_socket_dir, storage->port); storage->endpoints = od_malloc(1 * sizeof(od_storage_endpoint_t)); if (storage->endpoints == NULL) { return -1; } od_storage_endpoint_t *endpoint = &storage->endpoints[0]; endpoint->storage = storage; od_storage_endpoint_status_init(&endpoint->status); od_address_init(&endpoint->address); endpoint->address.type = OD_ADDRESS_TYPE_UNIX; endpoint->address.host = od_strdup(buff); if (endpoint->address.host == NULL) { od_free(storage->endpoints); return -1; } storage->endpoints_count = 1; } else { /* force default port */ for (size_t i = 0; i < storage->endpoints_count; ++i) { if (storage->endpoints[i].address.port == 0) { storage->endpoints[i].address.port = storage->port; } } } return 0; } int od_rules_validate(od_rules_t *rules, od_config_t *config, od_logger_t *logger) { /* storages */ if (od_list_empty(&rules->storages)) { od_error(logger, "rules", NULL, NULL, "no storage defined"); return -1; } od_list_t *i; od_list_foreach (&rules->storages, i) { od_rule_storage_t *storage; storage = od_container_of(i, od_rule_storage_t, link); if (storage->server_max_routing == 0) { storage->server_max_routing = config->workers; } if (storage->type == NULL) { od_error(logger, "rules", NULL, NULL, "storage '%s': no type is specified", storage->name); return -1; } if (strcmp(storage->type, "remote") == 0) { storage->storage_type = OD_RULE_STORAGE_REMOTE; } else if (strcmp(storage->type, "local") == 0) { storage->storage_type = OD_RULE_STORAGE_LOCAL; } else { od_error(logger, "rules", NULL, NULL, "unknown storage type"); return -1; } if (storage->storage_type == OD_RULE_STORAGE_REMOTE) { if (od_rules_validate_endpoints(logger, config, storage) != 0) { return -1; } } if (storage->tls_opts->tls) { if (strcmp(storage->tls_opts->tls, "disable") == 0) { storage->tls_opts->tls_mode = OD_CONFIG_TLS_DISABLE; } else if (strcmp(storage->tls_opts->tls, "allow") == 0) { storage->tls_opts->tls_mode = OD_CONFIG_TLS_ALLOW; od_log(logger, "rules", NULL, NULL, "storage '%s': tls \"allow\" now follows libpq sslmode=allow, " "a connection is made in plaintext and is retried with tls " "only after the server has rejected it; " "use tls \"prefer\" to always negotiate tls first", storage->name); } else if (strcmp(storage->tls_opts->tls, "prefer") == 0) { storage->tls_opts->tls_mode = OD_CONFIG_TLS_PREFER; } else if (strcmp(storage->tls_opts->tls, "require") == 0) { storage->tls_opts->tls_mode = OD_CONFIG_TLS_REQUIRE; } else if (strcmp(storage->tls_opts->tls, "verify_ca") == 0) { storage->tls_opts->tls_mode = OD_CONFIG_TLS_VERIFY_CA; } else if (strcmp(storage->tls_opts->tls, "verify_full") == 0) { storage->tls_opts->tls_mode = OD_CONFIG_TLS_VERIFY_FULL; } else { od_error(logger, "rules", NULL, NULL, "unknown storage tls_opts->tls mode"); return -1; } } } /* rules */ if (od_list_empty(&rules->rules)) { od_error(logger, "rules", NULL, NULL, "no rules defined"); return -1; } od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); /* match storage and make a copy of in the user rules */ if (rule->storage_name == NULL) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': no rule storage is specified", rule->db_name, rule->user_name, rule->address_range.string_value); return NOT_OK_RESPONSE; } od_rule_storage_t *storage; storage = od_rules_storage_match(rules, rule->storage_name); if (storage == NULL) { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': no rule storage '%s' found", rule->db_name, rule->user_name, rule->address_range.string_value, rule->storage_name); return NOT_OK_RESPONSE; } rule->storage = od_rules_storage_ref(storage); if (od_pool_validate(logger, rule->pool, rule->db_name, rule->user_name, &rule->address_range) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } if (rule->storage->storage_type != OD_RULE_STORAGE_LOCAL) { if (rule->user_role != OD_RULE_ROLE_UNDEF) { od_error( logger, "rules validate", NULL, NULL, "rule '%s.%s %s': role set for non-local storage", rule->db_name, rule->user_name, rule->address_range.string_value); return NOT_OK_RESPONSE; } } else { if (rule->user_role == OD_RULE_ROLE_UNDEF) { od_error( logger, "rules validate", NULL, NULL, "rule '%s.%s %s': force stat role for local storage", rule->db_name, rule->user_name, rule->address_range.string_value); rule->user_role = OD_RULE_ROLE_STAT; } } /* auth */ if (!rule->auth) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': authentication mode is not defined", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } if (strcmp(rule->auth, "none") == 0) { rule->auth_mode = OD_RULE_AUTH_NONE; } else if (strcmp(rule->auth, "block") == 0) { rule->auth_mode = OD_RULE_AUTH_BLOCK; } else if (strcmp(rule->auth, "clear_text") == 0) { rule->auth_mode = OD_RULE_AUTH_CLEAR_TEXT; #ifdef PAM_FOUND if (rule->auth_query != NULL && rule->auth_pam_service != NULL) { od_error( logger, "rules", NULL, NULL, "auth query and pam service auth method cannot be " "used simultaneously"); return -1; } #endif if (rule->enable_mdb_iamproxy_auth == 0 && rule->password == NULL && rule->auth_query == NULL #ifdef PAM_FOUND && rule->auth_pam_service == NULL #endif && rule->auth_module == NULL #ifdef LDAP_FOUND && rule->ldap_endpoint == NULL #endif ) { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': password is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } } else if (strcmp(rule->auth, "external") == 0) { rule->auth_mode = OD_RULE_AUTH_EXTERNAL; } else if (strcmp(rule->auth, "md5") == 0) { rule->auth_mode = OD_RULE_AUTH_MD5; if (rule->password == NULL && rule->auth_query == NULL) { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': password is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } } else if (strcmp(rule->auth, "scram-sha-256") == 0) { rule->auth_mode = OD_RULE_AUTH_SCRAM_SHA_256; if (rule->password == NULL && rule->auth_query == NULL) { od_error(logger, "rules", NULL, NULL, "rule '%s.%s %s': password is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } } else if (strcmp(rule->auth, "cert") == 0) { rule->auth_mode = OD_RULE_AUTH_CERT; } else { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': has unknown authentication mode", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } /* auth_query */ if (rule->auth_query) { if (rule->auth_query_user == NULL) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': auth_query_user is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } if (rule->auth_query_db == NULL) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': auth_query_db is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } } /* group */ if (rule->group) { if (rule->group->group_query == NULL) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': group_query is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } if (rule->group->group_query_user == NULL) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': group_query_user is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } if (rule->group->group_query_db == NULL) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s': group_query_db is not set", rule->db_name, rule->user_name, rule->address_range.string_value); return -1; } } #ifdef LDAP_FOUND if (rule->ldap_endpoint != NULL && config->coroutine_stack_size < LDAP_MIN_COROUTINE_STACK_SIZE) { od_error( logger, "rules", NULL, NULL, "rule '%s.%s %s' use ldap_endpoint. coroutine_stack_size must be >= %d", rule->db_name, rule->user_name, rule->address_range.string_value, LDAP_MIN_COROUTINE_STACK_SIZE); return -1; } #endif } return 0; } int od_rules_storage_merge(od_rules_t *dst, od_rules_t *src) { /* * Diff-based merge of storages from src into dst. * * For each storage in src, look for an equivalent storage in dst * (matched by name). If found and contents are identical, reuse * the old storage: re-point every rule in src that references the * new storage to the old one, then free the new copy. This keeps * the old storage's watchdog and endpoint statuses alive, avoiding * unnecessary reconnection churn. * * Storages in dst that are not present in src (or whose contents * changed) are unlinked from dst. Their watchdog (if running) is * signalled offline and joined before unref, so the coroutine * exits cleanly and drops its own ref. * * New storages (present in src but not in dst) are moved into dst. * * After this call, rules in src->rules reference storages that are * either in dst->storages (reused) or in src->storages (new), so * od_rules_merge which follows can safely compare origin->storage * and rule->storage. */ /* mark all dst storages as not-yet-matched */ od_list_t *i; od_list_foreach (&dst->storages, i) { od_rule_storage_t *s; s = od_container_of(i, od_rule_storage_t, link); s->mark = 0; } od_list_t *n; od_list_foreach_safe (&src->storages, i, n) { od_rule_storage_t *new_storage; new_storage = od_container_of(i, od_rule_storage_t, link); /* find equivalent storage in dst by name */ od_rule_storage_t *old_storage = NULL; od_list_t *j; od_list_foreach (&dst->storages, j) { od_rule_storage_t *s; s = od_container_of(j, od_rule_storage_t, link); if (strcmp(s->name, new_storage->name) == 0) { old_storage = s; break; } } if (old_storage && od_rules_storage_compare(old_storage, new_storage)) { /* * Storage is unchanged: reuse old storage. * Re-point all rules in src that reference * new_storage to old_storage. */ od_list_t *k; od_list_foreach (&src->rules, k) { od_rule_t *rule; rule = od_container_of(k, od_rule_t, link); if (rule->storage == new_storage) { od_rules_storage_unref(new_storage); rule->storage = od_rules_storage_ref( old_storage); } } /* * The new storage has a freshly allocated watchdog * that we don't need — the old storage already has * a running one. Free it. The watchdog coroutine * must not be running (it is started later, after * merge), so assert that. */ if (new_storage->watchdog) { od_assert(new_storage->watchdog_coro_id == -1); od_storage_watchdog_free(new_storage->watchdog); new_storage->watchdog = NULL; } /* free the new copy and drop it from src */ od_list_unlink(&new_storage->link); od_rules_storage_unref(new_storage); /* mark old storage as matched */ old_storage->mark = 1; } else { /* * Storage is new or changed: move new into dst. * Stop and join the old watchdog (if running) before * unref, so the watchdog coroutine exits cleanly * and drops its own ref. od_rules_storage_unref * no longer signals set_offline via refcount. */ if (old_storage) { if (old_storage->watchdog != NULL && old_storage->watchdog_coro_id != -1) { od_storage_watchdog_set_offline( old_storage->watchdog); machine_join( old_storage->watchdog_coro_id); } od_list_unlink(&old_storage->link); od_rules_storage_unref(old_storage); } od_list_unlink(&new_storage->link); od_rules_storage_add(dst, new_storage); } } /* * Unlink storages in dst that were not matched (not present in * new config), stop their watchdogs and unref them. */ od_list_foreach_safe (&dst->storages, i, n) { od_rule_storage_t *s; s = od_container_of(i, od_rule_storage_t, link); if (!s->mark) { if (s->watchdog != NULL && s->watchdog_coro_id != -1) { od_storage_watchdog_set_offline(s->watchdog); machine_join(s->watchdog_coro_id); } od_list_unlink(&s->link); od_rules_storage_unref(s); } else { s->mark = 0; } } return 0; } int od_rules_cleanup(od_rules_t *rules) { /* cleanup declarative storages rules data */ od_list_t *n, *i; od_list_foreach_safe (&rules->storages, i, n) { od_rule_storage_t *storage; storage = od_container_of(i, od_rule_storage_t, link); od_list_unlink(&storage->link); od_rules_storage_unref(storage); } od_list_init(&rules->storages); #ifdef LDAP_FOUND /* TODO: cleanup ldap od_list_foreach_safe(&rules->storages, i, n) { od_ldap_endpoint_t *endp; storage = od_container_of(i, od_ldap_endpoint_t, link); od_ldap_endpoint_free(endp); } od_list_init(&rules->ldap_endpoints);*/ #endif return 0; } static inline char *od_rules_yes_no(int value) { return value ? "yes" : "no"; } void od_rules_print(od_rules_t *rules, od_logger_t *logger) { od_list_t *i; od_log(logger, "config", NULL, NULL, "storages"); od_list_foreach (&rules->storages, i) { od_rule_storage_t *storage; storage = od_container_of(i, od_rule_storage_t, link); od_log(logger, "storage", NULL, NULL, " storage types %s", storage->storage_type == OD_RULE_STORAGE_REMOTE ? "remote" : "local"); od_log(logger, "storage", NULL, NULL, " host %s", storage->host ? storage->host : ""); od_log(logger, "storage", NULL, NULL, " port %d", storage->port); od_log(logger, "storage", NULL, NULL, " server_max_routing %d", storage->server_max_routing); od_log(logger, "storage", NULL, NULL, " endpoints_status_poll_interval_ms %d", storage->endpoints_status_poll_interval_ms); for (size_t ep_i = 0; ep_i < storage->endpoints_count; ep_i++) { char ep_str[256]; od_address_to_str(&storage->endpoints[ep_i].address, ep_str, sizeof(ep_str)); od_log(logger, "storage", NULL, NULL, " endpoint[%zu] %s", ep_i, ep_str); } if (storage->tls_opts->tls) { od_log(logger, "storage", NULL, NULL, " tls %s", storage->tls_opts->tls); } if (storage->tls_opts->tls_ca_file) { od_log(logger, "storage", NULL, NULL, " tls_ca_file %s", storage->tls_opts->tls_ca_file); } if (storage->tls_opts->tls_key_file) { od_log(logger, "storage", NULL, NULL, " tls_key_file %s", storage->tls_opts->tls_key_file); } if (storage->tls_opts->tls_cert_file) { od_log(logger, "storage", NULL, NULL, " tls_cert_file %s", storage->tls_opts->tls_cert_file); } if (storage->tls_opts->tls_protocols) { od_log(logger, "storage", NULL, NULL, " tls_protocols %s", storage->tls_opts->tls_protocols); } if (storage->watchdog) { if (storage->watchdog->query) { od_log(logger, "storage", NULL, NULL, " watchdog query %s", storage->watchdog->query); } if (storage->watchdog->interval) { od_log(logger, "storage", NULL, NULL, " watchdog interval %d", storage->watchdog->interval); } } } od_list_foreach (&rules->rules, i) { od_rule_t *rule; rule = od_container_of(i, od_rule_t, link); if (rule->obsolete) { continue; } od_log(logger, "rules", NULL, NULL, "<%s.%s %s %s>", rule->db_name, rule->user_name, rule->address_range.string_value, od_rule_conn_type_to_str(rule->conn_type)); od_log(logger, "rules", NULL, NULL, " authentication %s", rule->auth); if (rule->auth_common_name_default) { od_log(logger, "rules", NULL, NULL, " auth_common_name default"); } od_list_t *j; od_list_foreach (&rule->auth_common_names, j) { od_rule_auth_t *auth; auth = od_container_of(j, od_rule_auth_t, link); od_log(logger, "rules", NULL, NULL, " auth_common_name %s", auth->common_name); } if (rule->auth_query) { od_log(logger, "rules", NULL, NULL, " auth_query %s", rule->auth_query); } if (rule->auth_query_db) { od_log(logger, "rules", NULL, NULL, " auth_query_db %s", rule->auth_query_db); } if (rule->auth_query_user) { od_log(logger, "rules", NULL, NULL, " auth_query_user %s", rule->auth_query_user); } if (rule->auth_module) { od_log(logger, "rules", NULL, NULL, " auth_module %s", rule->auth_module); } { const char *role_str = "undef"; switch (rule->user_role) { case OD_RULE_ROLE_ADMIN: role_str = "admin"; break; case OD_RULE_ROLE_STAT: role_str = "stat"; break; case OD_RULE_ROLE_NOTALLOW: role_str = "notallow"; break; default: break; } od_log(logger, "rules", NULL, NULL, " role %s", role_str); } od_log(logger, "rules", NULL, NULL, " target_session_attrs %s", od_target_session_attrs_to_str( rule->target_session_attrs)); /* pool */ od_log(logger, "rules", NULL, NULL, " pool %s", rule->pool->pool_type_str); od_log(logger, "rules", NULL, NULL, " pool routing %s", rule->pool->routing_type == NULL ? "client visible" : rule->pool->routing_type); od_log(logger, "rules", NULL, NULL, " pool size %d", rule->pool->size); od_log(logger, "rules", NULL, NULL, " min pool size %d", rule->pool->min_size); od_log(logger, "rules", NULL, NULL, " pool timeout %d", rule->pool->timeout); od_log(logger, "rules", NULL, NULL, " pool ttl %d", rule->pool->ttl); od_log(logger, "rules", NULL, NULL, " pool discard %s", rule->pool->discard ? "yes" : "no"); if (rule->pool->discard_query) { od_log(logger, "rules", NULL, NULL, " pool discard_query %s", rule->pool->discard_query); } od_log(logger, "rules", NULL, NULL, " pool smart discard %s", rule->pool->smart_discard ? "yes" : "no"); od_log(logger, "rules", NULL, NULL, " pool cancel %s", rule->pool->cancel ? "yes" : "no"); od_log(logger, "rules", NULL, NULL, " pool rollback %s", rule->pool->rollback ? "yes" : "no"); od_log(logger, "rules", NULL, NULL, " pool client_idle_timeout %" PRIu64, rule->pool->client_idle_timeout); od_log(logger, "rules", NULL, NULL, " pool idle_in_transaction_timeout %" PRIu64, rule->pool->idle_in_transaction_timeout); od_log(logger, "rules", NULL, NULL, " pool reset_timeout_ms %" PRIu64, rule->pool->reset_timeout_ms); od_log(logger, "rules", NULL, NULL, " pool pin_on_listen %s", rule->pool->pin_on_listen ? "yes" : "no"); od_log(logger, "rules", NULL, NULL, " pool notice_after_waiting_ms %d", rule->pool->notice_after_waiting_ms); od_log(logger, "rules", NULL, NULL, " pool attach_check %s", rule->pool->attach_check ? "yes" : "no"); od_log(logger, "rules", NULL, NULL, " pool acquire_fail_fast %s", rule->pool->acquire_fail_fast ? "yes" : "no"); if (rule->pool->pool_type != OD_RULE_POOL_SESSION) { od_log(logger, "rules", NULL, NULL, " pool prepared statement support %s", rule->pool->reserve_prepared_statement ? "yes" : "no"); od_log(logger, "rules", NULL, NULL, " server pstmt cache size %d", rule->pool->server_pstmt_cache_size); } if (rule->client_max_set) { od_log(logger, "rules", NULL, NULL, " client_max %d", rule->client_max); } od_log(logger, "rules", NULL, NULL, " client_fwd_error %s", od_rules_yes_no(rule->client_fwd_error)); od_log(logger, "rules", NULL, NULL, " reserve_session_server_connection %s", od_rules_yes_no( rule->reserve_session_server_connection)); od_log(logger, "rules", NULL, NULL, " client_show_id %s", od_rules_yes_no(rule->client_show_id)); od_log(logger, "rules", NULL, NULL, " application_name_add_host %s", od_rules_yes_no(rule->application_name_add_host)); od_log(logger, "rules", NULL, NULL, " server_drop_on_cached_plan_error %s", od_rules_yes_no(rule->server_drop_on_cached_plan_error)); od_log(logger, "rules", NULL, NULL, " server_drop_on_oom %s", od_rules_yes_no(rule->server_drop_on_oom)); od_log(logger, "rules", NULL, NULL, " password_passthrough %s", od_rules_yes_no(rule->enable_password_passthrough)); od_log(logger, "rules", NULL, NULL, " server_lifetime_us %" PRIu64, rule->server_lifetime_us); #ifdef LDAP_FOUND if (rule->ldap_endpoint_name) { od_log(logger, "rules", NULL, NULL, " ldap_endpoint_name %s", rule->ldap_endpoint_name); } if (rule->ldap_storage_credentials_attr != NULL) { od_log(logger, "rules", NULL, NULL, " ldap_storage_credentials_attr %s", rule->ldap_storage_credentials_attr); } if (!od_list_empty(&rule->ldap_storage_creds_list)) { od_list_t *f; od_list_foreach (&rule->ldap_storage_creds_list, f) { od_ldap_storage_credentials_t *lsc; lsc = od_container_of( f, od_ldap_storage_credentials_t, link); if (lsc->name) { od_log(logger, "rule", NULL, NULL, " lsc_name %s", lsc->name); } if (lsc->lsc_username) { od_log(logger, "rule", NULL, NULL, " lsc_username %s", lsc->lsc_username); } if (lsc->lsc_password) { od_log(logger, "rule", NULL, NULL, " lsc_password %s", lsc->lsc_password); } } } #endif od_log(logger, "rules", NULL, NULL, " storage %s", rule->storage_name); od_log(logger, "rules", NULL, NULL, " type %s", rule->storage->type); od_log(logger, "rules", NULL, NULL, " host %s", rule->storage->host ? rule->storage->host : ""); od_log(logger, "rules", NULL, NULL, " port %d", rule->storage->port); if (rule->storage->tls_opts->tls) { od_log(logger, "rules", NULL, NULL, " tls_opts->tls %s", rule->storage->tls_opts->tls); } if (rule->storage->tls_opts->tls_ca_file) { od_log(logger, "rules", NULL, NULL, " tls_opts->tls_ca_file %s", rule->storage->tls_opts->tls_ca_file); } if (rule->storage->tls_opts->tls_key_file) { od_log(logger, "rules", NULL, NULL, " tls_opts->tls_key_file %s", rule->storage->tls_opts->tls_key_file); } if (rule->storage->tls_opts->tls_cert_file) { od_log(logger, "rules", NULL, NULL, " tls_opts->tls_cert_file %s", rule->storage->tls_opts->tls_cert_file); } if (rule->storage->tls_opts->tls_protocols) { od_log(logger, "rules", NULL, NULL, " tls_opts->tls_protocols %s", rule->storage->tls_opts->tls_protocols); } if (rule->storage_db) { od_log(logger, "rules", NULL, NULL, " storage_db %s", rule->storage_db); } if (rule->storage_user) { od_log(logger, "rules", NULL, NULL, " storage_user %s", rule->storage_user); } if (rule->catchup_timeout) { od_log(logger, "rules", NULL, NULL, " catchup timeout %d", rule->catchup_timeout); } if (rule->catchup_checks) { od_log(logger, "rules", NULL, NULL, " catchup checks %d", rule->catchup_checks); } od_log(logger, "rules", NULL, NULL, " maintain_params %s", od_rules_yes_no(rule->maintain_params)); od_log(logger, "rules", NULL, NULL, " log_debug %s", od_rules_yes_no(rule->log_debug)); od_log(logger, "rules", NULL, NULL, " log_query %s", od_rules_yes_no(rule->log_query)); for (int vi = 0; vi < KIWI_VAR_MAX; vi++) { kiwi_var_t *v = &rule->vars.vars[vi]; if (v->value_len > 0 && v->name) { od_log(logger, "rules", NULL, NULL, " pgoption %s=%s", v->name, v->value); } } for (size_t vi = 0; vi < rule->backend_startup_vars_sz; vi++) { untyped_kiwi_var_t *v = &rule->backend_startup_vars[vi]; if (v->name) { od_log(logger, "rules", NULL, NULL, " backend_startup_var %s=%s", v->name, v->value ? v->value : ""); } } od_log(logger, "rules", NULL, NULL, " keepalive %d", rule->keepalive); od_log(logger, "rules", NULL, NULL, " keepalive_keep_interval %d", rule->keepalive_keep_interval); od_log(logger, "rules", NULL, NULL, " keepalive_probes %d", rule->keepalive_probes); od_log(logger, "rules", NULL, NULL, " keepalive_usr_timeout %d", rule->keepalive_usr_timeout); } } /* Checks that the name matches the rule */ bool od_name_in_rule(const od_rule_t *rule, const char *name) { if (rule->group) { for (int i = 0; i < rule->users_in_group; i++) { if (strcmp(rule->user_names[i], name) == 0) { return true; } } return false; } return strcmp(rule->user_name, name) == 0; } odyssey-1.6.0-rc1/sources/scram.c000066400000000000000000000651571526064202200166510ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define od_b64_encode(src, src_len, dst, dst_len) \ pg_b64_encode(src, src_len, dst, dst_len); #define od_b64_decode(src, src_len, dst, dst_len) \ pg_b64_decode(src, src_len, dst, dst_len); struct pg_hmac_ctx { pg_cryptohash_ctx *hash; pg_cryptohash_type type; int block_size; int digest_size; /* * Use the largest block size among supported options. This wastes some * memory but simplifies the allocation logic. */ uint8 k_ipad[PG_SHA512_BLOCK_LENGTH]; uint8 k_opad[PG_SHA512_BLOCK_LENGTH]; }; typedef struct pg_hmac_ctx od_scram_ctx_t; #define od_scram_HMAC_init pg_hmac_init #define od_scram_HMAC_create() pg_hmac_create(PG_SHA256) #define od_scram_HMAC_update(ctx, str, slen) \ pg_hmac_update(ctx, (const uint8_t *)str, slen) #define od_scram_HMAC_final(dest, ctx) pg_hmac_final(ctx, dest, sizeof(dest)) #define od_scram_HMAC_free pg_hmac_free #define od_scram_ServerKey(salted_password, result, errstr) \ scram_ServerKey(salted_password, PG_SHA256, SCRAM_SHA_256_KEY_LEN, \ result, errstr) #define od_scram_SaltedPassword(password, salt, saltlen, iterations, result, \ errstr) \ scram_SaltedPassword(password, PG_SHA256, SCRAM_SHA_256_KEY_LEN, salt, \ saltlen, iterations, result, errstr) #define od_scram_H(input, len, result, errstr) \ scram_H(input, PG_SHA256, SCRAM_SHA_256_KEY_LEN, result, errstr) #define od_scram_ClientKey(salted_password, result, errstr) \ scram_ClientKey(salted_password, PG_SHA256, SCRAM_SHA_256_KEY_LEN, \ result, errstr) int od_scram_parse_verifier(od_scram_state_t *scram_state, char *verifier) { char *value = NULL; char *scheme = NULL; char *iterations_raw = NULL; char *salt_raw = NULL; char *stored_key_raw = NULL; char *server_key_raw = NULL; uint8_t *salt = NULL; uint8_t *stored_key = NULL; uint8_t *server_key = NULL; char *strtok_preserve = NULL; value = od_strdup(verifier); if (value == NULL) { return -1; } scheme = strtok_r(value, "$", &strtok_preserve); if (scheme == NULL) { goto error; } iterations_raw = strtok_r(NULL, ":", &strtok_preserve); if (iterations_raw == NULL) { goto error; } salt_raw = strtok_r(NULL, "$", &strtok_preserve); if (salt_raw == NULL) { goto error; } stored_key_raw = strtok_r(NULL, ":", &strtok_preserve); if (stored_key_raw == NULL) { goto error; } server_key_raw = strtok_r(NULL, "", &strtok_preserve); if (server_key_raw == NULL) { goto error; } if (strcmp(scheme, "SCRAM-SHA-256") != 0) { goto error; } char *end; int iterations = strtol(iterations_raw, &end, 10); if (*end != '\0' || iterations < 1) { goto error; } scram_state->iterations = iterations; int salt_raw_len = strlen(salt_raw); int salt_dst_len = pg_b64_dec_len(salt_raw_len); salt = od_malloc(salt_dst_len); if (salt == NULL) { goto error; } int salt_len = od_b64_decode(salt_raw, salt_raw_len, salt, salt_dst_len); od_free(salt); if (salt_len < 0) { goto error; } scram_state->salt = od_strdup(salt_raw); if (scram_state->salt == NULL) { goto error; } int stored_key_raw_len = strlen(stored_key_raw); int stored_key_dst_len = pg_b64_dec_len(stored_key_raw_len); stored_key = od_malloc(stored_key_dst_len); if (stored_key == NULL) { goto error; } int stored_key_len = od_b64_decode(stored_key_raw, stored_key_raw_len, stored_key, stored_key_dst_len); if (stored_key_len != OD_SCRAM_MAX_KEY_LEN) { goto error; } memcpy(scram_state->stored_key, stored_key, OD_SCRAM_MAX_KEY_LEN); int server_key_raw_len = strlen(server_key_raw); int server_key_dst_len = pg_b64_dec_len(server_key_raw_len); server_key = od_malloc(server_key_dst_len); if (server_key == NULL) { goto error; } int server_key_len = od_b64_decode(server_key_raw, server_key_raw_len, server_key, server_key_dst_len); if (server_key_len != OD_SCRAM_MAX_KEY_LEN) { goto error; } memcpy(scram_state->server_key, server_key, OD_SCRAM_MAX_KEY_LEN); od_free(stored_key); od_free(server_key); od_free(value); return 0; error: od_free(stored_key); od_free(server_key); od_free(value); scram_state->salt = NULL; return -1; } int od_scram_init_from_plain_password(od_scram_state_t *scram_state, char *plain_password) { char *prep_password = NULL; pg_saslprep_rc rc = pg_saslprep(plain_password, &prep_password); if (rc == SASLPREP_OOM) { goto error; } const char *password; if (rc == SASLPREP_SUCCESS) { password = prep_password; } else { password = plain_password; } uint8_t salt[SCRAM_DEFAULT_SALT_LEN]; RAND_bytes(salt, sizeof(salt)); scram_state->iterations = OD_SCRAM_SHA_256_DEFAULT_ITERATIONS; int salt_dst_len = pg_b64_enc_len(sizeof(salt)) + 1; scram_state->salt = od_malloc(salt_dst_len); if (!scram_state->salt) { goto error; } int base64_salt_len = od_b64_encode(salt, sizeof(salt), scram_state->salt, salt_dst_len); scram_state->salt[base64_salt_len] = '\0'; const char *errstr = NULL; /* usage exists depending of pg version */ (void)errstr; uint8_t salted_password[OD_SCRAM_MAX_KEY_LEN]; od_scram_SaltedPassword(password, salt, sizeof(salt), scram_state->iterations, salted_password, &errstr); od_scram_ClientKey(salted_password, scram_state->stored_key, &errstr); od_scram_H(scram_state->stored_key, OD_SCRAM_MAX_KEY_LEN, scram_state->stored_key, &errstr); od_scram_ServerKey(salted_password, scram_state->server_key, &errstr); if (prep_password) { od_free(prep_password); } return 0; error: if (prep_password) { od_free(prep_password); } return -1; } machine_msg_t * od_scram_create_client_first_message(od_scram_state_t *scram_state) { uint8_t nonce[SCRAM_RAW_NONCE_LEN]; RAND_bytes(nonce, SCRAM_RAW_NONCE_LEN); int client_nonce_dst_len = pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1; scram_state->client_nonce = od_malloc(client_nonce_dst_len); if (scram_state->client_nonce == NULL) { return NULL; } int base64_nonce_len = od_b64_encode(nonce, SCRAM_RAW_NONCE_LEN, scram_state->client_nonce, client_nonce_dst_len); scram_state->client_nonce[base64_nonce_len] = '\0'; size_t result_len = strlen("n,,n=,r=") + base64_nonce_len; char *result = od_malloc(result_len + 1); if (result == NULL) { goto error; } od_snprintf(result, result_len + 1, "n,,n=,r=%s", scram_state->client_nonce); scram_state->client_first_message = od_strdup(result + 3); if (scram_state->client_first_message == NULL) { goto error; } machine_msg_t *msg = kiwi_fe_write_authentication_sasl_initial( NULL, "SCRAM-SHA-256", result, result_len); if (msg == NULL) { goto error; } od_free(result); return msg; error: od_free(result); od_free(scram_state->client_nonce); od_free(scram_state->client_first_message); return NULL; } int read_server_first_message(od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size, char **server_nonce_ptr, size_t *server_nonce_size_ptr, uint8_t **salt_ptr, int *salt_len_ptr, int *iterations_ptr) { scram_state->server_first_message = od_strdup_from_buf(auth_data, auth_data_size); if (scram_state->server_first_message == NULL) { return -1; } char *server_nonce; size_t server_nonce_size; uint8_t *salt = NULL; if (read_attribute_buf(&auth_data, &auth_data_size, 'r', &server_nonce, &server_nonce_size)) { goto error; } char *client_nonce = scram_state->client_nonce; size_t client_nonce_len = strlen(client_nonce); if (server_nonce_size < client_nonce_len || memcmp(server_nonce, client_nonce, client_nonce_len) != 0) { goto error; } char *base64_salt; size_t base64_salt_size; if (read_attribute_buf(&auth_data, &auth_data_size, 's', &base64_salt, &base64_salt_size)) { goto error; } int salt_dst_len = pg_b64_dec_len(base64_salt_size) + 1; salt = od_malloc(salt_dst_len); if (salt == NULL) { goto error; } int salt_len = od_b64_decode(base64_salt, base64_salt_size, salt, salt_dst_len); if (salt_len < 0) { goto error; } salt[salt_len] = '\0'; char *iterations_raw; size_t iterations_raw_size; if (read_attribute_buf(&auth_data, &auth_data_size, 'i', &iterations_raw, &iterations_raw_size)) { goto error; } char *end = NULL; int iterations = od_memtol(iterations_raw, iterations_raw_size, &end, 10); if (end != iterations_raw + iterations_raw_size || auth_data_size || iterations < 1) { goto error; } *server_nonce_ptr = server_nonce; *server_nonce_size_ptr = server_nonce_size; *salt_ptr = salt; *salt_len_ptr = salt_len; *iterations_ptr = iterations; return 0; error: od_free(scram_state->server_first_message); od_free(salt); return -1; } static int calculate_client_proof(od_scram_state_t *scram_state, const char *password, const uint8_t *salt, int salt_len, int iterations, const char *client_final_message, uint8_t *client_proof) { const char *errstr = NULL; uint8_t client_key[OD_SCRAM_MAX_KEY_LEN]; if (scram_state->use_passthrough_keys) { memcpy(client_key, scram_state->client_key, OD_SCRAM_MAX_KEY_LEN); } else { char *prepared_password = NULL; pg_saslprep_rc rc = pg_saslprep(password, &prepared_password); if (rc == SASLPREP_OOM) { return -1; } if (rc != SASLPREP_SUCCESS) { prepared_password = od_strdup(password); } if (prepared_password == NULL) { return -1; } scram_state->salted_password = od_malloc(OD_SCRAM_MAX_KEY_LEN); if (scram_state->salted_password == NULL) { od_free(prepared_password); return -1; } od_scram_SaltedPassword(prepared_password, salt, salt_len, iterations, scram_state->salted_password, &errstr); od_scram_ClientKey(scram_state->salted_password, client_key, &errstr); od_free(prepared_password); } uint8_t stored_key[OD_SCRAM_MAX_KEY_LEN]; od_scram_H(client_key, OD_SCRAM_MAX_KEY_LEN, stored_key, &errstr); od_scram_ctx_t *ctx = od_scram_HMAC_create(); od_scram_HMAC_init(ctx, stored_key, OD_SCRAM_MAX_KEY_LEN); od_scram_HMAC_update(ctx, scram_state->client_first_message, strlen(scram_state->client_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->server_first_message, strlen(scram_state->server_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, client_final_message, strlen(client_final_message)); uint8_t client_signature[OD_SCRAM_MAX_KEY_LEN]; od_scram_HMAC_final(client_signature, ctx); for (int i = 0; i < OD_SCRAM_MAX_KEY_LEN; i++) { client_proof[i] = client_key[i] ^ client_signature[i]; } od_scram_HMAC_free(ctx); return 0; } static char *calculate_server_signature(od_scram_state_t *scram_state) { od_scram_ctx_t *ctx = od_scram_HMAC_create(); od_scram_HMAC_init(ctx, scram_state->server_key, OD_SCRAM_MAX_KEY_LEN); od_scram_HMAC_update(ctx, scram_state->client_first_message, strlen(scram_state->client_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->server_first_message, strlen(scram_state->server_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->client_final_message, strlen(scram_state->client_final_message)); uint8_t server_signature[OD_SCRAM_MAX_KEY_LEN]; od_scram_HMAC_final(server_signature, ctx); od_scram_HMAC_free(ctx); int base64_signature_dst_len = pg_b64_enc_len(OD_SCRAM_MAX_KEY_LEN) + 1; char *base64_signature = od_malloc(base64_signature_dst_len); if (base64_signature == NULL) { return NULL; } int base64_signature_len = od_b64_encode(server_signature, OD_SCRAM_MAX_KEY_LEN, base64_signature, base64_signature_dst_len); base64_signature[base64_signature_len] = '\0'; return base64_signature; } machine_msg_t * od_scram_create_client_final_message(od_scram_state_t *scram_state, char *password, char *auth_data, size_t auth_data_size) { char *server_nonce; size_t server_nonce_size; uint8_t *salt; int salt_len; int iterations; int rc = read_server_first_message(scram_state, auth_data, auth_data_size, &server_nonce, &server_nonce_size, &salt, &salt_len, &iterations); if (rc == -1) { return NULL; } #define SCRAM_FINAL_MAX_SIZE 512 char result[SCRAM_FINAL_MAX_SIZE]; char attributes[] = "c=biws,r="; if (sizeof(attributes) - 1 + server_nonce_size >= SCRAM_FINAL_MAX_SIZE) { return NULL; } memcpy(result, attributes, sizeof(attributes) - 1); memcpy(result + sizeof(attributes) - 1, server_nonce, server_nonce_size); result[sizeof(attributes) + server_nonce_size - 1] = '\0'; scram_state->client_final_message = od_strdup(result); if (scram_state->client_final_message == NULL) { return NULL; } uint8_t client_proof[OD_SCRAM_MAX_KEY_LEN]; rc = calculate_client_proof(scram_state, password, salt, salt_len, iterations, result, client_proof); od_free(salt); if (rc == -1) { goto error; } size_t size = 0; while (result[size] != '\0' && size < 508) { size++; } result[size++] = ','; result[size++] = 'p'; result[size++] = '='; size += od_b64_encode(client_proof, OD_SCRAM_MAX_KEY_LEN, result + size, SCRAM_FINAL_MAX_SIZE - size); #undef SCRAM_FINAL_MAX_SIZE result[size] = '\0'; machine_msg_t *msg = kiwi_fe_write_authentication_scram_final(NULL, result, size); if (msg == NULL) { goto error; } return msg; error: od_free(scram_state->client_final_message); return NULL; } int read_server_final_message(char *auth_data, size_t auth_data_size, uint8_t *server_signature) { if (!auth_data_size || *auth_data == 'e') { return -1; } char *signature; size_t signature_size; if (read_attribute_buf(&auth_data, &auth_data_size, 'v', &signature, &signature_size) || auth_data_size) { return -1; } int decoded_signature_len = pg_b64_dec_len(signature_size); uint8_t *decoded_signature = od_malloc(decoded_signature_len); if (decoded_signature == NULL) { return -1; } decoded_signature_len = od_b64_decode(signature, signature_size, decoded_signature, decoded_signature_len); if (decoded_signature_len != OD_SCRAM_MAX_KEY_LEN) { goto error; } memcpy(server_signature, decoded_signature, OD_SCRAM_MAX_KEY_LEN); od_free(decoded_signature); return 0; error: od_free(decoded_signature); return -1; } od_retcode_t od_scram_verify_server_signature(od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size) { uint8_t server_signature[SHA256_DIGEST_LENGTH]; od_retcode_t rc = read_server_final_message(auth_data, auth_data_size, server_signature); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } od_scram_ctx_t *ctx = od_scram_HMAC_create(); const char *errstr = NULL; /* usage exists depending of pg version */ (void)errstr; uint8_t server_key[OD_SCRAM_MAX_KEY_LEN]; if (scram_state->use_passthrough_keys) { memcpy(server_key, scram_state->server_key, OD_SCRAM_MAX_KEY_LEN); } else { od_scram_ServerKey(scram_state->salted_password, server_key, &errstr); } od_scram_HMAC_init(ctx, server_key, OD_SCRAM_MAX_KEY_LEN); od_scram_HMAC_update(ctx, scram_state->client_first_message, strlen(scram_state->client_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->server_first_message, strlen(scram_state->server_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->client_final_message, strlen(scram_state->client_final_message)); uint8_t expected_server_signature[SHA256_DIGEST_LENGTH]; od_scram_HMAC_final(expected_server_signature, ctx); od_scram_HMAC_free(ctx); if (memcmp(expected_server_signature, server_signature, SHA256_DIGEST_LENGTH) != 0) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } int od_scram_read_client_first_message(od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size) { if (!auth_data_size) { return -6; } switch (*auth_data) { case 'n': /* client without channel binding */ case 'y': /* client with channel binding */ auth_data++; auth_data_size--; break; case 'p': /* todo: client requires channel binding */ if (read_any_attribute_buf(&auth_data, &auth_data_size, NULL, NULL, NULL) == -1) { return -1; } auth_data--; auth_data_size++; break; default: return -2; } if (!auth_data_size || *auth_data != ',') { return -2; } auth_data++; auth_data_size--; if (!auth_data_size || *auth_data == 'a' || *auth_data != ',') { /* todo: authorization identity */ return OD_SASL_ERROR_AUTH_IDENTITY; } auth_data++; auth_data_size--; if (!auth_data_size || *auth_data == 'm') { /* todo: mandatory extensions */ return OD_SASL_ERROR_MANDATORY_EXT; } char *client_first_message = od_malloc(auth_data_size + 1); if (client_first_message == NULL) { return -1; } memcpy(client_first_message, auth_data, auth_data_size); client_first_message[auth_data_size] = '\0'; if (read_attribute_buf(&auth_data, &auth_data_size, 'n', NULL, NULL)) { goto error; } char *client_nonce; size_t client_nonce_size; if (read_attribute_buf(&auth_data, &auth_data_size, 'r', &client_nonce, &client_nonce_size)) { goto error; } for (size_t i = 0; i < client_nonce_size; i++) { char c = client_nonce[i]; if (c < 0x21 || c > 0x7E || c == ',') { goto error; } } { char *t = od_malloc(client_nonce_size + 1); if (t == NULL) { goto error; } memcpy(t, client_nonce, client_nonce_size); t[client_nonce_size] = '\0'; client_nonce = t; } while (auth_data_size) { if (read_any_attribute_buf(&auth_data, &auth_data_size, NULL, NULL, NULL) == -1) { goto error_free_client_nonce; } } scram_state->client_first_message = client_first_message; scram_state->client_nonce = client_nonce; return 0; error_free_client_nonce: od_free(client_nonce); error: od_free(client_first_message); return -1; } static inline int od_scram_need_channel_binding_check(const char *channel_binding, size_t channel_binding_size) { /* See pg's backend/libpq/auth-scram.c We need to perform channel binding check only if channel_binding is not equal to "biws" (=n,,) or "eSws" (=y,,) TODO: this is not totaly correct, maybe its better to get channel binding flag from client's first message? */ if (channel_binding_size != 4) { return 1; } if (memcmp(channel_binding, "biws", channel_binding_size) == 0 || memcmp(channel_binding, "eSws", channel_binding_size) == 0) { return 0; } return 1; } int od_scram_read_client_final_message(mm_io_t *io, od_scram_state_t *scram_state, char *auth_data, size_t auth_data_size, char **final_nonce_ptr, size_t *final_nonce_size_ptr, uint8_t **proof_ptr) { const char *input_start = auth_data; char *proof_start; char *base64_proof; size_t base64_proof_size; uint8_t *proof = NULL; unsigned char cbind_data[MM_CERT_HASH_LEN]; size_t cbind_data_len = 0; size_t cbind_header_len; char *cbind_input = NULL; size_t cbind_input_len; char *b64_message = NULL; int b64_message_len; int scram_rc; char *auth_data_copy = od_strdup_from_buf(auth_data, auth_data_size); if (auth_data_copy == NULL) { goto error; } char *channel_binding; size_t channel_binding_size; if (read_attribute_buf(&auth_data, &auth_data_size, 'c', &channel_binding, &channel_binding_size)) { goto error; } if (od_scram_need_channel_binding_check(channel_binding, channel_binding_size)) { /*channel binding check*/ /* need cert to perform check */ if (!mm_io_is_tls(io)) { goto error; } /* Fetch hash data of server's SSL certificate */ scram_rc = machine_tls_cert_hash( io, &cbind_data, (uint32_t *)&cbind_data_len); /* TODO: maybe rework of machinarium because it's strange that we use size_t here and uint32_t in machinarium */ /* should not happen */ if (scram_rc != OK_RESPONSE) { goto error; } cbind_header_len = strlen("p=tls-server-end-point,,"); /* p=type,, */ cbind_input_len = cbind_header_len + cbind_data_len; cbind_input = od_malloc(cbind_input_len); if (cbind_input == NULL) { goto error; } snprintf(cbind_input, cbind_input_len, "p=tls-server-end-point,,"); memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len); b64_message_len = pg_b64_enc_len(cbind_input_len); /* don't forget the zero-terminator */ b64_message = od_malloc(b64_message_len + 1); if (b64_message == NULL) { goto error; } b64_message_len = od_b64_encode((const uint8_t *)cbind_input, cbind_input_len, b64_message, b64_message_len); if (b64_message_len < 0) { /*elog(ERROR, "could not encode channel binding data"); */ goto error; } b64_message[b64_message_len] = '\0'; /* * Compare the value sent by the client with the value expected by the * server. */ if (strncmp(channel_binding, b64_message, b64_message_len) != 0) { /*ereport(ERROR, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("SCRAM channel binding check failed")));*/ goto error; } } char *client_final_nonce; size_t client_final_nonce_size; if (read_attribute_buf(&auth_data, &auth_data_size, 'r', &client_final_nonce, &client_final_nonce_size)) { goto error; } char attribute; do { proof_start = auth_data - 1; if (read_any_attribute_buf(&auth_data, &auth_data_size, &attribute, &base64_proof, &base64_proof_size)) { goto error; } } while (attribute != 'p'); int proof_size = pg_b64_dec_len(base64_proof_size); proof = od_malloc(proof_size); if (proof == NULL) { goto error; } int proof_len = od_b64_decode(base64_proof, base64_proof_size, proof, proof_size); if (proof_len != SCRAM_SHA_256_KEY_LEN) { goto error; } if (auth_data_size) { goto error; } scram_state->client_final_message = od_malloc(proof_start - input_start + 1); if (!scram_state->client_final_message) { goto error; } memcpy(scram_state->client_final_message, auth_data_copy, proof_start - input_start); if (cbind_input) { od_free(cbind_input); } if (b64_message) { od_free(b64_message); } od_free(auth_data_copy); scram_state->client_final_message[proof_start - input_start] = '\0'; *final_nonce_ptr = client_final_nonce; *final_nonce_size_ptr = client_final_nonce_size; *proof_ptr = proof; return 0; error: if (cbind_input) { od_free(cbind_input); } if (b64_message) { od_free(b64_message); } if (proof) { od_free(proof); } if (auth_data_copy) { od_free(auth_data_copy); } return -1; } machine_msg_t * od_scram_create_server_first_message(od_scram_state_t *scram_state) { char *result; uint8_t nonce[SCRAM_RAW_NONCE_LEN + 1]; RAND_bytes(nonce, SCRAM_RAW_NONCE_LEN); int server_nonce_len = pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1; scram_state->server_nonce = od_malloc(server_nonce_len); if (scram_state->server_nonce == NULL) { goto error; } int base64_nonce_len = od_b64_encode(nonce, SCRAM_RAW_NONCE_LEN, scram_state->server_nonce, server_nonce_len); scram_state->server_nonce[base64_nonce_len] = '\0'; size_t size = 12 + strlen(scram_state->client_nonce) + strlen(scram_state->server_nonce) + strlen(scram_state->salt); result = od_malloc(size + 1); if (!result) { goto error; } snprintf(result, size + 1, "r=%s%s,s=%s,i=%u", scram_state->client_nonce, scram_state->server_nonce, scram_state->salt, scram_state->iterations); scram_state->server_first_message = result; return kiwi_be_write_authentication_sasl_continue(NULL, result, size); error: od_free(scram_state->server_nonce); scram_state->server_nonce = NULL; od_free(scram_state->server_first_message); scram_state->server_first_message = NULL; return NULL; } od_retcode_t od_scram_verify_final_nonce(od_scram_state_t *scram_state, char *final_nonce, size_t final_nonce_size) { size_t client_nonce_len = strlen(scram_state->client_nonce); size_t server_nonce_len = strlen(scram_state->server_nonce); if (final_nonce_size != client_nonce_len + server_nonce_len) { return NOT_OK_RESPONSE; } if (memcmp(final_nonce, scram_state->client_nonce, client_nonce_len) != 0) { return NOT_OK_RESPONSE; } if (memcmp(final_nonce + client_nonce_len, scram_state->server_nonce, server_nonce_len) != 0) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } od_retcode_t od_scram_verify_client_proof(od_scram_state_t *scram_state, uint8_t *client_proof) { uint8_t client_signature[OD_SCRAM_MAX_KEY_LEN]; uint8_t client_stored_key[OD_SCRAM_MAX_KEY_LEN]; od_scram_ctx_t *ctx = od_scram_HMAC_create(); const char *errstr = NULL; /* usage exists depending of pg version */ (void)errstr; od_scram_HMAC_init(ctx, scram_state->stored_key, OD_SCRAM_MAX_KEY_LEN); od_scram_HMAC_update(ctx, scram_state->client_first_message, strlen(scram_state->client_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->server_first_message, strlen(scram_state->server_first_message)); od_scram_HMAC_update(ctx, ",", 1); od_scram_HMAC_update(ctx, scram_state->client_final_message, strlen(scram_state->client_final_message)); od_scram_HMAC_final(client_signature, ctx); for (int i = 0; i < OD_SCRAM_MAX_KEY_LEN; i++) { scram_state->client_key[i] = client_proof[i] ^ client_signature[i]; } od_scram_H(scram_state->client_key, OD_SCRAM_MAX_KEY_LEN, client_stored_key, &errstr); od_scram_HMAC_free(ctx); if (memcmp(client_stored_key, scram_state->stored_key, OD_SCRAM_MAX_KEY_LEN) != 0) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } machine_msg_t * od_scram_create_server_final_message(od_scram_state_t *scram_state) { char *signature = calculate_server_signature(scram_state); if (signature == NULL) { return NULL; } size_t size = strlen("v=") + strlen(signature); char *result = od_malloc(size + 1); if (result == NULL) { goto error; } /* * There is compiler warning about some wierd case * when snprintf result is above INT_MAX * (we dont check snprintf result, see -Wformat-truncation) */ if (od_unlikely(size + 1 >= INT_MAX)) { abort(); } snprintf(result, size + 1, "v=%s", signature); od_free(signature); machine_msg_t *msg = kiwi_be_write_authentication_sasl_final(NULL, result, size); if (msg == NULL) { goto error; } od_free(result); return msg; error: od_free(result); return NULL; } odyssey-1.6.0-rc1/sources/server.c000066400000000000000000000100071526064202200170320ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include static inline void od_server_free_now(od_server_t *server) { od_io_close(&server->io); od_io_free(&server->io); if (server->endpoint != NULL) { od_rules_storage_unref(server->endpoint->storage); server->endpoint = NULL; } if (server->tls != NULL) { machine_tls_free(server->tls); server->tls = NULL; } if (server->prep_stmts) { od_server_pstmts_free(server); } od_scram_state_free(&server->scram_state); od_free(server); } static inline int64_t od_server_ref(od_server_t *server) { return atomic_fetch_add(&server->refs, 1); } static inline int64_t od_server_unref(od_server_t *server) { int64_t prev = atomic_fetch_sub(&server->refs, 1); if (prev == 1) { od_server_free_now(server); } return prev; } void od_server_free(od_server_t *server) { od_server_unref(server); } void od_server_cancel_begin(od_server_t *server) { od_server_ref(server); } void od_server_cancel_end(od_server_t *server) { int64_t prev_refs = od_server_unref(server); if (prev_refs == 2) { /* * only cancels or "pool" holds the server * * if only cancels holds the server, it state * and pool_element will be UNDEF and NULL respectively, * (all cases of closing server means state=UNDEF) * in that case nothing to be done - the server is closed * and removed from pool by the router, and the next * cancel end will free it * * if the cancel and the pool holds the server, it can be * return to IDLE state and reused by another client * * the cases can de separated by server->state value */ if (server->state == OD_SERVER_ACTIVE) { od_server_set_pool_state(server, OD_SERVER_IDLE); } } else { /* * if prev_refs > 2, then * nothing can be done - the client is not detached * or there is another cancels for this server * * if prev_refs == 1, then * server was removed from pool * while the cancel was performed * ex: еhe connection failed and detached from client * * cancel was the only one who used the server, and now * this unref freed the server - nothing to be done now */ } } void od_server_attach_client(od_server_t *server, od_client_t *client) { od_assert(server->client == NULL); od_assert(client->server == NULL); od_assert(server->state != OD_SERVER_ACTIVE); server->client = client; client->server = server; server->key_client = client->key; server->idle_time = 0; od_server_set_pool_state(server, OD_SERVER_ACTIVE); od_server_ref(server); } void od_server_detach_client(od_server_t *server) { od_client_t *client = server->client; od_assert(client != NULL); od_assert(server->state == OD_SERVER_ACTIVE); od_assert(server == client->server); client->server = NULL; server->client = NULL; /* * this can lead to errors in PG like: * PID 0 in cancel request did not match any process * * we currently believe that this is not real problem * because it is better to have extra message in PG log * that does nothing than sending cancel to another * than current client */ kiwi_key_init(&server->key_client); if (od_server_unref(server) == 2) { /* * set idle only if no cancel refs the server * otherwise it will continue to be ACTIVE - no clients can * acquire this server */ od_server_set_pool_state(server, OD_SERVER_IDLE); } } od_server_pool_t *od_server_pool(od_server_t *server) { return &server->pool_element->pool; } const od_address_t *od_server_pool_address(od_server_t *server) { return &server->pool_element->key.address; } void od_server_set_pool_state(od_server_t *server, od_server_state_t state) { od_server_pool_t *pool; pool = od_server_pool(server); od_pg_server_pool_set(pool, server, state); if (state == OD_SERVER_UNDEF) { server->pool_element = NULL; if (server->endpoint != NULL) { od_rules_storage_unref(server->endpoint->storage); server->endpoint = NULL; } } } odyssey-1.6.0-rc1/sources/setproctitle.c000066400000000000000000000013141526064202200202460ustar00rootroot00000000000000#include #include #include #include od_retcode_t od_setproctitlef(char **argv_ptr, int argv_len, char *fmt, ...) { if (argv_ptr == NULL || *argv_ptr == NULL || argv_len <= 0) { return NOT_OK_RESPONSE; } char title[OD_MAX_PROC_TITLE_LEN]; va_list args; va_start(args, fmt); int title_len = od_vsnprintf(title, sizeof(title), fmt, args); va_end(args); if (title_len < 0) { return NOT_OK_RESPONSE; } title[title_len] = '\0'; memset(*argv_ptr, 0, argv_len); size_t copy_len = title_len; if (copy_len > (size_t)argv_len - 1) { copy_len = (size_t)argv_len - 1; } memcpy(*argv_ptr, title, copy_len + 1); return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/shared_pool.c000066400000000000000000000023361526064202200200310ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include static inline void od_shared_pool_free(od_shared_pool_t *sp) { od_list_unlink(&sp->link); od_multi_pool_destroy(sp->mpool); od_free(sp->name); mm_spinlock_destroy(&sp->lock); od_free(sp); } od_shared_pool_t *od_shared_pool_create(const char *name) { od_shared_pool_t *sp = od_malloc(sizeof(od_shared_pool_t)); if (sp == NULL) { return NULL; } memset(sp, 0, sizeof(od_shared_pool_t)); sp->name = strdup(name); if (sp->name == NULL) { od_free(sp); return NULL; } sp->refs = 1; sp->mpool = od_multi_pool_create(od_pg_server_pool_free); if (sp->mpool == NULL) { od_free(sp->name); od_free(sp); return NULL; } od_list_init(&sp->link); mm_spinlock_init(&sp->lock); return sp; } od_shared_pool_t *od_shared_pool_ref(od_shared_pool_t *sp) { mm_spinlock_lock(&sp->lock); ++sp->refs; mm_spinlock_unlock(&sp->lock); return sp; } void od_shared_pool_unref(od_shared_pool_t *sp) { int refs; mm_spinlock_lock(&sp->lock); refs = sp->refs--; mm_spinlock_unlock(&sp->lock); if (refs == 1) { od_shared_pool_free(sp); } } odyssey-1.6.0-rc1/sources/sighandler.c000066400000000000000000000166121526064202200176540ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct waiter_arg { od_system_t *system; machine_channel_t *channel; } waiter_arg_t; static inline void od_signal_waiter(void *arg) { waiter_arg_t *waiter_arg = arg; od_system_t *system = waiter_arg->system; machine_channel_t *channel = waiter_arg->channel; od_instance_t *instance = system->global->instance; for (;;) { int rc; rc = machine_signal_wait(UINT32_MAX); /* canceled */ if (rc == -1) { break; } machine_msg_t *msg = machine_msg_create(sizeof(int)); if (msg == NULL) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to create a message in sigwaiter"); } int *data = machine_msg_data(msg); *data = rc; machine_msg_set_type(msg, OD_MSG_SIGNAL_RECEIVED); machine_channel_write(channel, msg); } } void od_system_signal_handler(void *arg) { od_system_t *system = arg; od_instance_t *instance = system->global->instance; pid_t new_binary_pid = -1; pid_t wpid = -1; int wstatus = -1; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGCHLD); sigaddset(&mask, OD_SIG_LOG_ROTATE); sigaddset(&mask, OD_SIG_ONLINE_RESTART); sigaddset(&mask, SIGWINCH); sigset_t ignore_mask; sigemptyset(&ignore_mask); sigaddset(&ignore_mask, SIGPIPE); int rc; rc = machine_signal_init(&mask, &ignore_mask); if (rc == -1) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to init signal handler (machine_signal_init)"); } machine_channel_t *channel; channel = machine_channel_create(); if (channel == NULL) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to init signal handler (channel creation)"); } waiter_arg_t waiter_arg = { system, channel }; int sigwaiter_id = machine_coroutine_create_named( od_signal_waiter, &waiter_arg, "sigwaiter"); if (sigwaiter_id == -1) { od_fatal( &instance->logger, "system", NULL, NULL, "failed to init signal handler (signal waiter creation)"); } int term_count = 0; bool shutting_down = false; while (!shutting_down) { machine_msg_t *msg = machine_channel_read(channel, UINT32_MAX); /* canceled */ if (msg == NULL) { od_log(&instance->logger, "system", NULL, NULL, "NULL message in sighandler"); break; } int type = machine_msg_type(msg); if (type != OD_MSG_SIGNAL_RECEIVED) { od_assert(0); machine_msg_free(msg); continue; } int sig = *(int *)machine_msg_data(msg); machine_msg_free(msg); switch (sig) { case SIGTERM: case SIGINT: od_log(&instance->logger, "system", NULL, NULL, "termination signal received, shutting down"); if (++term_count >= instance->config.max_sigterms_to_die) { exit(1); } /* * If we're being replaced by a new process (online restart), * notify systemd of the new main PID before shutting down. */ if (new_binary_pid != -1) { od_systemd_notify_mainpid(new_binary_pid); /* signal the new binary to set ready */ kill(new_binary_pid, SIGWINCH); } else { /* Notify systemd we're shutting down */ od_systemd_notify_stopping(); } if (od_system_send_msg(system, OD_MSG_SHUTDOWN, NULL) != 0) { od_error(&instance->logger, "system", NULL, NULL, "failed to send shutdown request, " "forcing exit"); exit(1); } shutting_down = true; break; case SIGWINCH: /* * old binary accepted our term signal and setup MAINPID * now we must notify that we are ready */ if (instance->pid.restart_ppid == -1) { online_restart_log( "got unexpected SIGWINCH, ignored"); break; } od_systemd_notify_ready(); break; case SIGHUP: od_log(&instance->logger, "system", NULL, NULL, "SIGHUP received"); if (new_binary_pid != -1) { od_log(&instance->logger, "system", NULL, NULL, "performing online restart now, SIGHUP is ignored"); break; } od_systemd_notify_reloading("Reloading configuration"); mm_wait_flag_t *reload_done = mm_wait_flag_create(); if (reload_done == NULL) { od_error(&instance->logger, "system", NULL, NULL, "failed to create wait flag for " "reload, notifying ready anyway"); od_systemd_notify_ready(); break; } if (od_system_send_msg(system, OD_MSG_RELOAD, reload_done) != 0) { mm_wait_flag_destroy(reload_done); od_error(&instance->logger, "system", NULL, NULL, "failed to send reload request, " "notifying ready anyway"); od_systemd_notify_ready(); break; } mm_wait_flag_wait(reload_done, UINT32_MAX); mm_wait_flag_destroy(reload_done); od_systemd_notify_ready(); break; case OD_SIG_LOG_ROTATE: if (instance->config.log_file) { od_log(&instance->logger, "system", NULL, NULL, "SIGUSR1 received, reopening log"); od_logger_reopen(&instance->logger); } break; case OD_SIG_ONLINE_RESTART: online_restart_log("online restart signal received"); if (!instance->config.enable_online_restart_feature) { online_restart_error( "online restart signal ignored - feature is disabled in config"); break; } if (new_binary_pid != -1) { online_restart_error( "online restart signal ignored - already spawning new binary"); break; } if (getppid() == instance->pid.restart_ppid) { online_restart_error( "online restart signal ignored - parent odyssey process is still alive"); break; } od_systemd_notify_reloading( "Graceful restart on new binary"); new_binary_pid = od_restart_run_new_binary(); if (new_binary_pid != -1) { online_restart_log("new binary pid = %d", new_binary_pid); od_global_get_instance()->pid.restart_new_pid = new_binary_pid; } else { /* notify ready because no one else will - the child didnt start */ od_systemd_notify_ready(); online_restart_error( "running new binary failed - keep use old instance"); } break; case SIGCHLD: wpid = waitpid(-1, &wstatus, WNOHANG); if (wpid == -1) { od_gerror("system", NULL, NULL, "waitpid failed: %s", strerror(errno)); break; } if (wpid == 0) { break; } /* currently SIGCHLD is tracked to only catch if new binary failed to start */ if (wpid != new_binary_pid) { od_glog("system", NULL, NULL, "waitpid returned unexpected pid(%d), ignore (the only expected is %d)", wpid, new_binary_pid); break; } /* notify ready because no one else will - the child crashed */ od_systemd_notify_ready(); if (WIFEXITED(wstatus)) { online_restart_error( "new binary exited(%d) - keep use old binary instance", WEXITSTATUS(wstatus)); new_binary_pid = -1; } else if (WIFSIGNALED(wstatus)) { online_restart_error( "new binary was killed by signal(%d) - keep use old binary instance", WTERMSIG(wstatus)); new_binary_pid = -1; } /* all other wait status is ignored */ break; } } machine_cancel(sigwaiter_id); machine_join(sigwaiter_id); machine_channel_free(channel); } odyssey-1.6.0-rc1/sources/soft_oom.c000066400000000000000000000243421526064202200173600ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #define SOFT_OOM_LOG_CONTEXT "soft-oom" #define PROC_DIR_PATH "/proc" #define PROC_COMM_FMT "/proc/%d/comm" #define PROC_SMAPS_ROLLUP "/proc/%d/smaps_rollup" #define PROC_MEMINFO "/proc/meminfo" #define POSTGRES_COMM "postgres" static inline int od_soft_oom_get_system_memory_consumption(uint64_t *result) { FILE *fp = fopen(PROC_MEMINFO, "re"); if (!fp) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't open '%s': %s", PROC_MEMINFO, strerror(errno)); return NOT_OK_RESPONSE; } char line[256]; int64_t mem_available = 0, mem_total = 0; while (fgets(line, sizeof(line), fp)) { if (sscanf(line, "MemAvailable: %" PRId64 " kB", &mem_available) == 1) { continue; } if (sscanf(line, "MemTotal: %" PRId64 " kB", &mem_total) == 1) { continue; } } fclose(fp); int64_t mem_used = mem_total - mem_available; if (mem_used >= 0) { *result = ((uint64_t)mem_used); od_glog(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "updated memory consumption for system: %" PRIu64 " KB", *result); } else { od_glog(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "got negative mem used: total = %" PRId64 ", available = %" PRId64, mem_total, mem_available); *result = 0; } return OK_RESPONSE; } static inline void strstrip_right(char *s) { size_t size; char *end; size = strlen(s); if (!size) { return; } end = s + size - 1; while (end >= s && isspace(*end)) { end--; } *(end + 1) = '\0'; } static inline int od_soft_oom_is_target_process(const char *target_comm, pid_t pid, char *pid_comm, int *result) { char comm_path[256]; char comm[32]; snprintf(comm_path, sizeof(comm_path), PROC_COMM_FMT, pid); FILE *fp = fopen(comm_path, "re"); if (fp == NULL) { return NOT_OK_RESPONSE; } if (fgets(comm, sizeof(comm), fp) == NULL) { fclose(fp); return NOT_OK_RESPONSE; } *result = 0; if (strstr(comm, target_comm) != NULL) { *result = 1; strstrip_right(comm); strcpy(pid_comm, comm); } fclose(fp); return OK_RESPONSE; } static inline int od_soft_oom_accumulate_process_pss(pid_t pid, uint64_t *result) { char smaps_path[256]; snprintf(smaps_path, sizeof(smaps_path), PROC_SMAPS_ROLLUP, pid); FILE *smaps = fopen(smaps_path, "re"); if (smaps == NULL) { return NOT_OK_RESPONSE; } char line[256]; size_t pss_kb = 0; while (fgets(line, sizeof(line), smaps)) { if (sscanf(line, "Pss:%zu", &pss_kb) == 1) { break; } } fclose(smaps); *result += (pss_kb * 1024); return OK_RESPONSE; } typedef int (*od_soft_oom_proc_cb)(pid_t pid, const char *comm, void *arg); static inline int od_soft_oom_iterate_procs_by_comm(const char *comm, od_soft_oom_proc_cb cb, void *arg) { DIR *proc_dir = opendir(PROC_DIR_PATH); if (proc_dir == NULL) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't open " PROC_DIR_PATH ": %s", strerror(errno)); return NOT_OK_RESPONSE; } int rc = OK_RESPONSE; while (1) { struct dirent *entry = readdir(proc_dir); if (entry == NULL) { break; } if (entry->d_type != DT_DIR) { continue; } pid_t pid = atoi(entry->d_name); if (pid <= 0) { continue; } int is_target = 0; char pid_comm[32]; if (od_soft_oom_is_target_process(comm, pid, pid_comm, &is_target) != OK_RESPONSE) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't check pid %d: %s", pid, strerror(errno)); continue; } if (!is_target) { continue; } rc = cb(pid, pid_comm, arg); if (rc != OK_RESPONSE) { break; } } closedir(proc_dir); return rc; } typedef struct { uint64_t *result; int npids; } aggregate_pss_cb_arg_t; static inline int od_soft_oom_aggregate_consumption_cb(pid_t pid, const char *comm, void *arg) { aggregate_pss_cb_arg_t *carg = arg; if (od_soft_oom_accumulate_process_pss(pid, carg->result) != OK_RESPONSE) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't accumulate pss from pid %d (%s): %s", pid, comm, strerror(errno)); /* return ok in order to not stop on errors */ return OK_RESPONSE; } ++carg->npids; return OK_RESPONSE; } typedef struct { uint64_t pss; char comm[32]; pid_t pid; } proc_pss_info_t; int proc_pss_info_desc_cmp(const void *a, const void *b) { const proc_pss_info_t *aa = a; const proc_pss_info_t *bb = b; /* can't do substraction here because numbers are unsigned? */ if (aa->pss > bb->pss) { return -1; } if (aa->pss < bb->pss) { return 1; } return 0; } typedef struct { proc_pss_info_t *infos; size_t count; size_t capacity; } list_procs_arg_t; static inline void list_procs_arg_init(list_procs_arg_t *arg) { arg->capacity = 0; arg->count = 0; arg->infos = NULL; } static inline void list_procs_arg_destroy(list_procs_arg_t *arg) { od_free(arg->infos); } static inline int list_procs_add(list_procs_arg_t *arg, pid_t pid, const char *comm, uint64_t pss) { if (arg->count >= arg->capacity) { arg->capacity = arg->capacity ? (arg->capacity * 2) : 32; arg->infos = od_realloc( arg->infos, arg->capacity * sizeof(proc_pss_info_t)); if (arg->infos == NULL) { return NOT_OK_RESPONSE; } } proc_pss_info_t *info = &arg->infos[arg->count++]; info->pid = pid; info->pss = pss; strcpy(info->comm, comm); return OK_RESPONSE; } static inline int od_soft_oom_list_procs_cb(pid_t pid, const char *comm, void *arg) { list_procs_arg_t *larg = arg; uint64_t pss = 0; if (od_soft_oom_accumulate_process_pss(pid, &pss) != OK_RESPONSE) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't accumulate pss from pid %d: %s", pid, strerror(errno)); /* return ok in order to not stop on errors of pss accumulate */ return OK_RESPONSE; } /* can return not ok - any next add will fail if so */ return list_procs_add(larg, pid, comm, pss); } static inline int od_soft_oom_get_mem_consumption_from_processes(od_config_soft_oom_t *config, uint64_t *result) { aggregate_pss_cb_arg_t arg = { .npids = 0, .result = result }; int rc = od_soft_oom_iterate_procs_by_comm( config->process, od_soft_oom_aggregate_consumption_cb, &arg); od_glog(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "updated memory consumption for '%s' (%d pids): %" PRIu64 " bytes", config->process, arg.npids, *result); return rc; } static inline int od_soft_oom_get_mem_consumption(od_config_soft_oom_t *config, uint64_t *result) { if (config->process[0]) { return od_soft_oom_get_mem_consumption_from_processes(config, result); } return od_soft_oom_get_system_memory_consumption(result); } static inline void od_soft_oom_signal_postgres(od_soft_oom_checker_t *checker) { od_config_soft_oom_t *config = checker->config; od_config_soft_oom_drop_t *drop = &checker->config->drop; if (!drop->enabled) { return; } uint64_t used_bytes = 0; if (!od_soft_oom_is_in_soft_oom(checker, &used_bytes)) { return; } od_glog(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "used memory (%" PRIu64 ") >= limit (%" PRIu64 "), need to signal %d to top %d pss consumers", used_bytes, config->limit_bytes, drop->signal, drop->max_rate); list_procs_arg_t arg; list_procs_arg_init(&arg); int rc = od_soft_oom_iterate_procs_by_comm( POSTGRES_COMM, od_soft_oom_list_procs_cb, &arg); if (rc != OK_RESPONSE) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "fail to build pss list"); list_procs_arg_destroy(&arg); return; } qsort(arg.infos, arg.count, sizeof(proc_pss_info_t), proc_pss_info_desc_cmp); for (size_t i = 0; i < arg.count && i < (size_t)drop->max_rate; ++i) { proc_pss_info_t *info = &arg.infos[i]; od_glog(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "sending %d signal to %d (%s)...", drop->signal, info->pid, info->comm); if (kill(info->pid, drop->signal) == -1) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't send signal to %d: %s", info->pid, strerror(errno)); } } list_procs_arg_destroy(&arg); } static inline void od_soft_oom_checker(void *arg) { od_soft_oom_checker_t *checker = arg; while (1) { int rc = machine_wait_flag_wait( checker->stop_flag, checker->config->check_interval_ms); if (rc == 0) { od_glog(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "stop flag is set, exiting soft oom checker"); break; } int errno_ = mm_errno_get(); if (rc == -1 && errno_ != ETIMEDOUT) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "stop flag wait failed: %s (%d)", strerror(errno_), errno_); machine_sleep(checker->config->check_interval_ms); continue; } uint64_t used_mem = 0; if (od_soft_oom_get_mem_consumption(checker->config, &used_mem) != OK_RESPONSE) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "memory state update failed"); continue; } atomic_store(&checker->current_memory_usage, used_mem); od_soft_oom_signal_postgres(checker); } } int od_soft_oom_start_checker(od_config_soft_oom_t *config, od_soft_oom_checker_t *checker) { machine_wait_flag_t *stop_flag = machine_wait_flag_create(); if (stop_flag == NULL) { return NOT_OK_RESPONSE; } atomic_store(&checker->current_memory_usage, 0); checker->stop_flag = stop_flag; checker->config = config; /* * yes, we pass not fully-initialized checker into thread * but that is ok because we dont use machine_id inside new thread */ checker->machine_id = machine_create("soft-oom-worker", od_soft_oom_checker, checker); if (checker->machine_id == -1) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "can't create machine for soft oom checks"); machine_wait_flag_destroy(checker->stop_flag); return NOT_OK_RESPONSE; } return OK_RESPONSE; } void od_soft_oom_stop_checker(od_soft_oom_checker_t *checker) { if (checker->machine_id == 0) { return; } machine_wait_flag_set(checker->stop_flag); machine_wait(checker->machine_id); machine_wait_flag_destroy(checker->stop_flag); checker->current_memory_usage = 0; checker->machine_id = 0; checker->stop_flag = NULL; } int od_soft_oom_is_in_soft_oom(od_soft_oom_checker_t *checker, uint64_t *used_memory) { *used_memory = atomic_load(&checker->current_memory_usage); return *used_memory >= checker->config->limit_bytes; } odyssey-1.6.0-rc1/sources/sql/000077500000000000000000000000001526064202200161615ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/sql/minimal/000077500000000000000000000000001526064202200176075ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/sql/minimal/ast.c000066400000000000000000000075431526064202200205530ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Minimal SQL parser AST helpers. */ #include #include #include #include od_sql_minimal_node_t *od_sql_minimal_node_alloc(od_linear_alloc_t *al, od_sql_minimal_node_tag_t type, size_t size) { od_sql_minimal_node_t *n = od_linear_alloc_alloc(al, size, OD_LINEAR_ALLOC_ZERO); if (n == NULL) { return NULL; } n->type = type; return n; } void od_sql_minimal_node_free(od_sql_minimal_node_t *node) { /* nodes are arena-allocated; freeing is done by od_linear_alloc_reset */ (void)node; } int od_sql_minimal_node_print(const od_sql_minimal_node_t *node, char *buf, size_t buflen) { if (node == NULL) { return snprintf(buf, buflen, "(null)"); } switch (node->type) { case OD_SQL_MINIMAL_NODE_TYPE_SHOW_STMT: { const od_sql_minimal_show_stmt_t *n = (const od_sql_minimal_show_stmt_t *)node; return snprintf(buf, buflen, "(show %s)", n->name ? n->name : ""); } case OD_SQL_MINIMAL_NODE_TYPE_SET_STMT: { const od_sql_minimal_set_stmt_t *n = (const od_sql_minimal_set_stmt_t *)node; if (n->value) { return snprintf(buf, buflen, "(set %s=%s)", n->key ? n->key : "", n->value); } return snprintf(buf, buflen, "(set %s=default)", n->key ? n->key : ""); } case OD_SQL_MINIMAL_NODE_TYPE_BEGIN_STMT: return snprintf(buf, buflen, "(begin)"); case OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT: { const od_sql_minimal_deallocate_stmt_t *n = (const od_sql_minimal_deallocate_stmt_t *)node; if (n->is_all) { return snprintf(buf, buflen, "(deallocate all)"); } return snprintf(buf, buflen, "(deallocate %s)", n->name ? n->name : ""); } case OD_SQL_MINIMAL_NODE_TYPE_UNLISTEN_STMT: { const od_sql_minimal_unlisten_stmt_t *n = (const od_sql_minimal_unlisten_stmt_t *)node; if (n->is_all) { return snprintf(buf, buflen, "(unlisten *)"); } return snprintf(buf, buflen, "(unlisten %s)", n->name ? n->name : ""); } case OD_SQL_MINIMAL_NODE_TYPE_DISCARD_STMT: { const od_sql_minimal_discard_stmt_t *n = (const od_sql_minimal_discard_stmt_t *)node; switch (n->target) { case OD_SQL_MINIMAL_DISCARD_ALL: return snprintf(buf, buflen, "(discard all)"); case OD_SQL_MINIMAL_DISCARD_TEMP: return snprintf(buf, buflen, "(discard temp)"); case OD_SQL_MINIMAL_DISCARD_PLANS: return snprintf(buf, buflen, "(discard plans)"); case OD_SQL_MINIMAL_DISCARD_SEQUENCES: return snprintf(buf, buflen, "(discard sequences)"); } return snprintf(buf, buflen, "(discard unknown)"); } default: return snprintf(buf, buflen, "(unknown:%d)", (int)node->type); } } void od_sql_minimal_extract_query_ctx(const od_sql_minimal_node_t *ast, od_query_ctx_t *ctx) { ctx->is_discard_all = 0; ctx->is_unlisten_all = 0; ctx->is_deallocate_all = 0; ctx->has_deallocate_name = 0; ctx->deallocate_name[0] = '\0'; ctx->parse_error = 0; if (ast == NULL) { ctx->parse_error = 1; return; } switch (ast->type) { case OD_SQL_MINIMAL_NODE_TYPE_UNLISTEN_STMT: { const od_sql_minimal_unlisten_stmt_t *n = (const od_sql_minimal_unlisten_stmt_t *)ast; ctx->is_unlisten_all = n->is_all; break; } case OD_SQL_MINIMAL_NODE_TYPE_DISCARD_STMT: { const od_sql_minimal_discard_stmt_t *n = (const od_sql_minimal_discard_stmt_t *)ast; ctx->is_discard_all = (n->target == OD_SQL_MINIMAL_DISCARD_ALL); break; } case OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT: { const od_sql_minimal_deallocate_stmt_t *n = (const od_sql_minimal_deallocate_stmt_t *)ast; if (n->is_all) { ctx->is_deallocate_all = 1; } else if (n->name != NULL) { ctx->has_deallocate_name = 1; od_snprintf(ctx->deallocate_name, sizeof(ctx->deallocate_name), "%s", n->name); } break; } default: break; } } void od_query_ctx_reset(od_query_ctx_t *ctx) { memset(ctx, 0, sizeof(od_query_ctx_t)); } odyssey-1.6.0-rc1/sources/sql/minimal/ctx.c000066400000000000000000000011161526064202200205500ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Minimal SQL parser context. */ #include #include void od_sql_minimal_parse_ctx_init(od_sql_minimal_parse_ctx_t *ctx, const char *input, size_t input_len, od_linear_alloc_t *arena, od_sql_minimal_error_cb_t error_cb, void *userdata) { memset(ctx, 0, sizeof(*ctx)); if (input_len == 0) { input_len = strlen(input); } ctx->scanbuf = input; ctx->scanbuflen = input_len; ctx->arena = arena; ctx->error_cb = error_cb; ctx->error_cb_userdata = userdata; } odyssey-1.6.0-rc1/sources/sql/minimal/keywords.c000066400000000000000000000022421526064202200216220ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include typedef struct { const char *name; int token; } od_sql_minimal_keyword_t; static const od_sql_minimal_keyword_t keywords[] = { /* session / admin commands */ { "set", KW_SET }, { "show", KW_SHOW }, { "local", KW_LOCAL }, { "session", KW_SESSION }, { "to", KW_TO }, { "default", KW_DEFAULT }, { "all", KW_ALL }, { "true", KW_TRUE }, { "false", KW_FALSE }, { "on", KW_ON }, { "off", KW_OFF }, { "begin", KW_BEGIN }, /* transaction control */ { "work", KW_WORK }, { "transaction", KW_TRANSACTION }, { "deallocate", KW_DEALLOCATE }, { "prepare", KW_PREPARE }, { "discard", KW_DISCARD }, { "temp", KW_TEMP }, { "temporary", KW_TEMPORARY }, { "plans", KW_PLANS }, { "sequences", KW_SEQUENCES }, { "unlisten", KW_UNLISTEN }, { NULL, 0 } }; int od_sql_minimal_keyword_lookup(const char *str, size_t len) { for (size_t i = 0; keywords[i].name != NULL; i++) { if (strlen(keywords[i].name) == len && strncasecmp(keywords[i].name, str, len) == 0) { return keywords[i].token; } } return 0; } odyssey-1.6.0-rc1/sources/sql/minimal/parse.y000066400000000000000000000223771526064202200211260ustar00rootroot00000000000000%start top %define api.pure full %define api.prefix {od_sql_minimal_yy} %define parse.error verbose %locations %parse-param { yyscan_t scanner } %parse-param { od_sql_minimal_parse_ctx_t *ctx } %lex-param { yyscan_t scanner } %code top { #undef YYLLOC_DEFAULT #define YYLLOC_DEFAULT(cur, rhs, n) \ ((cur) = (n) ? YYRHSLOC(rhs, 1) : YYRHSLOC(rhs, 0)) } %code requires { #include #include #include #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void *yyscan_t; #endif typedef struct od_sql_minimal_parse_ctx od_sql_minimal_parse_ctx_t; #ifndef YYSTYPE #define YYSTYPE OD_SQL_MINIMAL_YYSTYPE #endif /* * Override bison's default YYLTYPE (struct with first_line/column etc.) * with a simple int (byte offset). This goes into parse.tab.h and is * visible to both bison (.tab.c) and flex (scan.c via scan.h). */ #undef YYLTYPE typedef int od_sql_minimal_yyltype_t; #define YYLTYPE od_sql_minimal_yyltype_t } %code { /* Bison defines #define YYLTYPE OD_SQL_MINIMAL_YYLTYPE above this * block. Override it with our int type after the fact. * Also disable YYLTYPE_IS_TRIVIAL so bison doesn't emit * struct-style { 1,1,1,1 } initializers for an int. */ #undef YYLTYPE #undef OD_SQL_MINIMAL_YYLTYPE_IS_TRIVIAL #define OD_SQL_MINIMAL_YYLTYPE_IS_TRIVIAL 0 typedef int od_sql_minimal_yyltype_t; #define YYLTYPE od_sql_minimal_yyltype_t #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #include #include static void od_sql_minimal_yyerror(YYLTYPE *loc, yyscan_t scanner, od_sql_minimal_parse_ctx_t *ctx, const char *msg) { (void)loc; (void)scanner; ctx->had_error = 1; if (ctx->error_cb) { ctx->error_cb(msg, ctx->error_cb_userdata); } } static char *arena_str(od_sql_minimal_parse_ctx_t *ctx, const char *src, size_t len) { char *s = od_linear_alloc_alloc(ctx->arena, len + 1, 0); if (s == NULL) { return NULL; } memcpy(s, src, len); s[len] = '\0'; return s; } static char *arena_strcat(od_sql_minimal_parse_ctx_t *ctx, const char *a, size_t la, char sep, const char *b, size_t lb) { char *s = od_linear_alloc_alloc(ctx->arena, la + 1 + lb + 1, 0); if (s == NULL) { return NULL; } memcpy(s, a, la); s[la] = sep; memcpy(s + la + 1, b, lb); s[la + 1 + lb] = '\0'; return s; } #define ALLOC_NODE(ctx, type_name, enum_val) \ ((od_sql_minimal_ ## type_name ## _stmt_t *) \ od_sql_minimal_node_alloc((ctx)->arena, enum_val, \ sizeof(od_sql_minimal_ ## type_name ## _stmt_t))) } %union { char *str; int64_t ival; od_sql_minimal_node_t *node; od_sql_minimal_discard_target_t dtarget; } %token ERROR_TOKEN %token INTEGER %token SCONST %token IDENT /* session / admin */ %token KW_SET %token KW_SHOW %token KW_LOCAL %token KW_SESSION %token KW_TO %token KW_DEFAULT %token KW_ALL %token KW_TRUE %token KW_FALSE %token KW_ON %token KW_OFF %token KW_BEGIN /* transaction control (only BEGIN) */ %token KW_WORK %token KW_TRANSACTION /* prepared statements */ %token KW_DEALLOCATE %token KW_PREPARE /* discard */ %token KW_DISCARD %token KW_TEMP %token KW_TEMPORARY %token KW_PLANS %token KW_SEQUENCES %token KW_UNLISTEN %type stmt %type set_stmt %type show_stmt %type begin_stmt %type deallocate_stmt %type discard_stmt %type unlisten_stmt %type var_name %type var_value %type col_id %type discard_target %destructor { /* arena-allocated, no-op */ } %destructor { od_sql_minimal_node_free($$); } %% top: stmt opt_semicolon { ctx->result = $1; } | opt_semicolon { ctx->result = NULL; } ; opt_semicolon: %empty | ';' ; stmt: set_stmt | show_stmt | begin_stmt | deallocate_stmt | discard_stmt | unlisten_stmt ; /* * SET [SESSION|LOCAL] name { TO | = } { value | DEFAULT } */ set_stmt: KW_SET opt_set_scope var_name KW_TO var_value { od_sql_minimal_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_SQL_MINIMAL_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $3; $3 = NULL; n->value = $5; $5 = NULL; $$ = (od_sql_minimal_node_t *)n; } | KW_SET opt_set_scope var_name '=' var_value { od_sql_minimal_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_SQL_MINIMAL_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $3; $3 = NULL; n->value = $5; $5 = NULL; $$ = (od_sql_minimal_node_t *)n; } | KW_SET opt_set_scope var_name KW_TO KW_DEFAULT { od_sql_minimal_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_SQL_MINIMAL_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $3; $3 = NULL; n->value = NULL; $$ = (od_sql_minimal_node_t *)n; } | KW_SET opt_set_scope var_name '=' KW_DEFAULT { od_sql_minimal_set_stmt_t *n = ALLOC_NODE(ctx, set, OD_SQL_MINIMAL_NODE_TYPE_SET_STMT); if (n == NULL) YYABORT; n->key = $3; $3 = NULL; n->value = NULL; $$ = (od_sql_minimal_node_t *)n; } ; opt_set_scope: %empty | KW_SESSION | KW_LOCAL ; show_stmt: KW_SHOW var_name { od_sql_minimal_show_stmt_t *n = ALLOC_NODE(ctx, show, OD_SQL_MINIMAL_NODE_TYPE_SHOW_STMT); if (n == NULL) YYABORT; n->name = $2; $2 = NULL; $$ = (od_sql_minimal_node_t *)n; } ; /* * BEGIN [WORK|TRANSACTION] */ begin_stmt: KW_BEGIN opt_transaction { od_sql_minimal_begin_stmt_t *n = ALLOC_NODE(ctx, begin, OD_SQL_MINIMAL_NODE_TYPE_BEGIN_STMT); if (n == NULL) YYABORT; $$ = (od_sql_minimal_node_t *)n; } ; opt_transaction: %empty | KW_WORK | KW_TRANSACTION ; /* * DEALLOCATE [PREPARE] { name | ALL } */ deallocate_stmt: KW_DEALLOCATE IDENT { od_sql_minimal_deallocate_stmt_t *n = ALLOC_NODE(ctx, deallocate, OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT); if (n == NULL) YYABORT; n->name = $2; $2 = NULL; n->is_all = 0; $$ = (od_sql_minimal_node_t *)n; } | KW_DEALLOCATE KW_PREPARE IDENT { od_sql_minimal_deallocate_stmt_t *n = ALLOC_NODE(ctx, deallocate, OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT); if (n == NULL) YYABORT; n->name = $3; $3 = NULL; n->is_all = 0; $$ = (od_sql_minimal_node_t *)n; } | KW_DEALLOCATE KW_ALL { od_sql_minimal_deallocate_stmt_t *n = ALLOC_NODE(ctx, deallocate, OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT); if (n == NULL) YYABORT; n->name = NULL; n->is_all = 1; $$ = (od_sql_minimal_node_t *)n; } | KW_DEALLOCATE KW_PREPARE KW_ALL { od_sql_minimal_deallocate_stmt_t *n = ALLOC_NODE(ctx, deallocate, OD_SQL_MINIMAL_NODE_TYPE_DEALLOCATE_STMT); if (n == NULL) YYABORT; n->name = NULL; n->is_all = 1; $$ = (od_sql_minimal_node_t *)n; } ; /* * DISCARD { ALL | TEMP | TEMPORARY | PLANS | SEQUENCES } */ discard_stmt: KW_DISCARD discard_target { od_sql_minimal_discard_stmt_t *n = ALLOC_NODE(ctx, discard, OD_SQL_MINIMAL_NODE_TYPE_DISCARD_STMT); if (n == NULL) YYABORT; n->target = $2; $$ = (od_sql_minimal_node_t *)n; } ; discard_target: KW_ALL { $$ = OD_SQL_MINIMAL_DISCARD_ALL; } | KW_TEMP { $$ = OD_SQL_MINIMAL_DISCARD_TEMP; } | KW_TEMPORARY { $$ = OD_SQL_MINIMAL_DISCARD_TEMP; } | KW_PLANS { $$ = OD_SQL_MINIMAL_DISCARD_PLANS; } | KW_SEQUENCES { $$ = OD_SQL_MINIMAL_DISCARD_SEQUENCES; } ; /* * UNLISTEN { identifier | * } */ unlisten_stmt: KW_UNLISTEN '*' { od_sql_minimal_unlisten_stmt_t *n = ALLOC_NODE(ctx, unlisten, OD_SQL_MINIMAL_NODE_TYPE_UNLISTEN_STMT); if (n == NULL) YYABORT; n->name = NULL; n->is_all = 1; $$ = (od_sql_minimal_node_t *)n; } | KW_UNLISTEN col_id { od_sql_minimal_unlisten_stmt_t *n = ALLOC_NODE(ctx, unlisten, OD_SQL_MINIMAL_NODE_TYPE_UNLISTEN_STMT); if (n == NULL) YYABORT; n->name = $2; $2 = NULL; n->is_all = 0; $$ = (od_sql_minimal_node_t *)n; } ; var_name: col_id { $$ = $1; } | var_name '.' col_id { size_t la = strlen($1), lb = strlen($3); $$ = arena_strcat(ctx, $1, la, '.', $3, lb); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } ; var_value: SCONST { $$ = $1; $1 = NULL; } | col_id { $$ = $1; } | KW_ON { $$ = arena_str(ctx, "on", 2); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } | KW_OFF { $$ = arena_str(ctx, "off", 3); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } | KW_TRUE { $$ = arena_str(ctx, "true", 4); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } | KW_FALSE { $$ = arena_str(ctx, "false", 5); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } | INTEGER { char tmp[24]; int n = snprintf(tmp, sizeof(tmp), "%" PRId64, $1); $$ = arena_str(ctx, tmp, (size_t)n); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } ; col_id: IDENT { $$ = $1; $1 = NULL; } | KW_ALL { $$ = arena_str(ctx, "all", 3); if ($$ == NULL) { od_sql_minimal_yyerror(&yylloc, scanner, ctx, "out of memory"); YYABORT; } } ; %% odyssey-1.6.0-rc1/sources/sql/minimal/parser.c000066400000000000000000000032651526064202200212550ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. * * Minimal SQL parser entry point. */ #include #include #include #include #include od_sql_minimal_node_t *od_sql_minimal_parse(const char *input, size_t input_len, od_linear_alloc_t *arena, od_sql_minimal_error_cb_t error_cb, void *userdata) { if (input == NULL) { return NULL; } od_linear_alloc_reset(arena, 0); od_sql_minimal_parse_ctx_t ctx; od_sql_minimal_parse_ctx_init(&ctx, input, input_len, arena, error_cb, userdata); /* * set up longjmp recovery before calling flex, if the scanner * hits OOM (arena full), YY_FATAL_ERROR does longjmp back here * instead of calling exit(1). We treat it as a normal parse error */ ctx.fatal_set = 1; if (setjmp(ctx.fatal_jmp) != 0) { /* * scanner hit a fatal error (most likely OOM in arena) * return NULL as a normal parse failure */ return NULL; } yyscan_t scanner; if (od_sql_minimal_yylex_init_extra(&ctx, &scanner) != 0) { if (error_cb) { error_cb("failed to initialize scanner", userdata); } return NULL; } YY_BUFFER_STATE buf = od_sql_minimal_yy_scan_bytes( ctx.scanbuf, (int)ctx.scanbuflen, scanner); if (buf == NULL) { if (error_cb) { error_cb("failed to create scanner buffer", userdata); } od_sql_minimal_yylex_destroy(scanner); return NULL; } int rc = od_sql_minimal_yyparse(scanner, &ctx); od_sql_minimal_yy_delete_buffer(buf, scanner); od_sql_minimal_yylex_destroy(scanner); if (rc != 0 || ctx.had_error) { od_sql_minimal_node_free(ctx.result); return NULL; } return ctx.result; } odyssey-1.6.0-rc1/sources/sql/minimal/scan.l000066400000000000000000000102401526064202200207050ustar00rootroot00000000000000%option reentrant %option bison-bridge %option bison-locations %option noyywrap %option nodefault %option warn %option never-interactive %option noyyalloc %option noyyrealloc %option noyyfree %option prefix="od_sql_minimal_yy" %option extra-type="od_sql_minimal_parse_ctx_t *" %top{ /* flex-generated code triggers sign-compare warnings */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" #include #include #include #include #include #define PG_NAMEDATALEN 64 /* * override flex's YY_FATAL_ERROR so that OOM in the scanner does not * call exit(1) — instead longjmp back to od_sql_minimal_parse(), which * returns NULL as a normal parse error. */ #define YY_FATAL_ERROR(msg) do { \ od_sql_minimal_parse_ctx_t *_ctx = od_sql_minimal_yyget_extra(yyscanner); \ if (_ctx && _ctx->fatal_set) { \ _ctx->had_error = 1; \ if (_ctx->error_cb) { \ _ctx->error_cb(msg, _ctx->error_cb_userdata); \ } \ longjmp(_ctx->fatal_jmp, 1); \ } \ fprintf(stderr, "%s\n", msg); \ od_assert(0); \ } while (0) } %{ #define SET_YYLLOC() \ (*yylloc = (int)(yytext - yyextra->scanbuf)) void *od_sql_minimal_yyalloc(yy_size_t bytes, yyscan_t yyscanner); void *od_sql_minimal_yyrealloc(void *ptr, yy_size_t bytes, yyscan_t yyscanner); void od_sql_minimal_yyfree(void *ptr, yyscan_t yyscanner); %} DIGIT [0-9] IDENT_START [A-Za-z_] IDENT_CONT [A-Za-z0-9_] IDENT {IDENT_START}{IDENT_CONT}* INTEGER -?{DIGIT}+ %% [ \t\r\n]+ { SET_YYLLOC(); /* skip */ } "--".* { SET_YYLLOC(); /* skip line comment */ } "/*" { SET_YYLLOC(); int c; while ((c = input(yyscanner)) != 0) { if (c == '*') { int next = input(yyscanner); if (next == 0) break; if (next == '/') goto comment_done; unput(next); } } comment_done:; /* skip block comment */ } ";" { SET_YYLLOC(); return ';'; } "=" { SET_YYLLOC(); return '='; } "," { SET_YYLLOC(); return ','; } "(" { SET_YYLLOC(); return '('; } ")" { SET_YYLLOC(); return ')'; } "." { SET_YYLLOC(); return '.'; } "*" { SET_YYLLOC(); return '*'; } {INTEGER} { SET_YYLLOC(); errno = 0; int64_t v = strtoll(yytext, NULL, 10); if (errno == ERANGE) { return ERROR_TOKEN; } yylval->ival = v; return INTEGER; } \'([^\'\\]|\\.)*\' { SET_YYLLOC(); size_t len = (size_t)yyleng - 2; od_linear_alloc_t *_arena = yyextra->arena; char *s = od_linear_alloc_alloc(_arena, len + 1, 0); if (s == NULL) { return ERROR_TOKEN; } memcpy(s, yytext + 1, len); s[len] = '\0'; yylval->str = s; return SCONST; } \"([^\"\\]|\\.)*\" { SET_YYLLOC(); /* double-quoted identifier -- strip quotes, preserve case */ size_t len = (size_t)yyleng - 2; if (len > PG_NAMEDATALEN - 1) { return ERROR_TOKEN; } od_linear_alloc_t *_arena = yyextra->arena; char *s = od_linear_alloc_alloc(_arena, len + 1, 0); if (s == NULL) { return ERROR_TOKEN; } memcpy(s, yytext + 1, len); s[len] = '\0'; yylval->str = s; return IDENT; } {IDENT} { SET_YYLLOC(); int tok = od_sql_minimal_keyword_lookup(yytext, (size_t)yyleng); if (tok != 0) { return tok; } od_linear_alloc_t *_arena = yyextra->arena; char *s = od_linear_alloc_alloc(_arena, (size_t)yyleng + 1, 0); if (s == NULL) { return ERROR_TOKEN; } for (int _i = 0; _i < yyleng; _i++) { s[_i] = (char)tolower(yytext[_i]); } s[yyleng] = '\0'; yylval->str = s; return IDENT; } . { SET_YYLLOC(); return ERROR_TOKEN; } %% void * od_sql_minimal_yyalloc(yy_size_t bytes, yyscan_t yyscanner) { od_sql_minimal_parse_ctx_t *ctx = od_sql_minimal_yyget_extra(yyscanner); return od_linear_alloc_alloc(ctx->arena, (size_t)bytes, OD_LINEAR_ALLOC_ZERO); } void * od_sql_minimal_yyrealloc(void *ptr, yy_size_t bytes, yyscan_t yyscanner) { (void)ptr; (void)bytes; (void)yyscanner; abort(); } void od_sql_minimal_yyfree(void *ptr, yyscan_t yyscanner) { /* arena-owned memory -- nothing to do */ (void)ptr; (void)yyscanner; } odyssey-1.6.0-rc1/sources/storage.c000066400000000000000000000264451526064202200172050ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include void od_storage_endpoint_status_init(od_storage_endpoint_status_t *status) { status->last_update_time_ms = 0ULL; status->alive = 1; status->is_read_write = true; status->repl_lag_sec = 0; mm_spinlock_init(&status->values_lock); } void od_storage_endpoint_status_destroy(od_storage_endpoint_status_t *status) { mm_spinlock_destroy(&status->values_lock); } bool od_storage_endpoint_status_is_outdated( od_storage_endpoint_status_t *status, uint64_t recheck_interval) { mm_spinlock_lock(&status->values_lock); uint64_t last_update_time_ms = status->last_update_time_ms; mm_spinlock_unlock(&status->values_lock); return (machine_time_ms() - last_update_time_ms) > recheck_interval; } void od_storage_endpoint_status_get(od_storage_endpoint_status_t *status, od_storage_endpoint_status_t *out) { mm_spinlock_lock(&status->values_lock); out->last_update_time_ms = status->last_update_time_ms; out->is_read_write = status->is_read_write; out->alive = status->alive; out->repl_lag_sec = status->repl_lag_sec; mm_spinlock_unlock(&status->values_lock); } void od_storage_endpoint_status_set(od_storage_endpoint_status_t *status, const od_storage_endpoint_status_t *value) { mm_spinlock_lock(&status->values_lock); status->last_update_time_ms = value->last_update_time_ms; status->is_read_write = value->is_read_write; status->alive = value->alive; status->repl_lag_sec = value->repl_lag_sec; mm_spinlock_unlock(&status->values_lock); } void od_storage_endpoint_status_set_dead(od_storage_endpoint_status_t *status) { od_storage_endpoint_status_t endp_status; od_storage_endpoint_status_init(&endp_status); endp_status.alive = 0; endp_status.is_read_write = 0; endp_status.last_update_time_ms = machine_time_ms(); endp_status.repl_lag_sec = 0; od_storage_endpoint_status_set(status, &endp_status); } od_storage_watchdog_t *od_storage_watchdog_allocate(od_global_t *global) { od_storage_watchdog_t *watchdog; watchdog = od_malloc(sizeof(od_storage_watchdog_t)); if (watchdog == NULL) { return NULL; } memset(watchdog, 0, sizeof(od_storage_watchdog_t)); watchdog->global = global; watchdog->online = machine_wait_flag_create(); if (watchdog->online == NULL) { od_free(watchdog); return NULL; } return watchdog; } int od_storage_watchdog_set_offline(od_storage_watchdog_t *watchdog) { machine_wait_flag_set(watchdog->online); return OK_RESPONSE; } int od_storage_watchdog_free(od_storage_watchdog_t *watchdog) { if (watchdog == NULL) { return NOT_OK_RESPONSE; } if (watchdog->query) { od_free(watchdog->query); } if (watchdog->route_usr) { od_free(watchdog->route_usr); } if (watchdog->route_db) { od_free(watchdog->route_db); } machine_wait_flag_destroy(watchdog->online); od_free(watchdog); return OK_RESPONSE; } od_rule_storage_t *od_rules_storage_allocate(void) { /* Allocate and force defaults */ od_rule_storage_t *storage = (od_rule_storage_t *)od_malloc(sizeof(od_rule_storage_t)); if (storage == NULL) { return NULL; } memset(storage, 0, sizeof(*storage)); storage->watchdog_coro_id = -1; storage->tls_opts = od_tls_opts_alloc(); if (storage->tls_opts == NULL) { od_free(storage); return NULL; } atomic_init(&storage->refs, 1); storage->endpoints_status_poll_interval_ms = 1000; od_storage_balancing_init(&storage->balancing); od_list_init(&storage->link); return storage; } static void storage_free_now(od_rule_storage_t *storage) { if (storage->name) { od_free(storage->name); } if (storage->type) { od_free(storage->type); } if (storage->host) { od_free(storage->host); } if (storage->tls_opts) { od_tls_opts_free(storage->tls_opts); } if (storage->endpoints_count) { for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_status_destroy( &storage->endpoints[i].status); od_address_destroy(&storage->endpoints[i].address); } od_free(storage->endpoints); } od_storage_balancing_destroy(&storage->balancing); od_free(storage); } void od_rules_storage_unref(od_rule_storage_t *storage) { int64_t r = atomic_fetch_sub(&storage->refs, 1); od_release_assert(r >= 1); if (r > 1) { return; } /* r == 1: last ref. If a watchdog was allocated but never * started, free it now (it holds no coroutine ref). * * If the watchdog was started, it has already been joined by * the caller (shutdown or reload path) before dropping the * last non-watchdog ref. By the time we get here, the watchdog * coroutine has already exited, nulled storage->watchdog, and * dropped its own ref. So storage->watchdog is NULL here. */ if (storage->watchdog != NULL) { od_storage_watchdog_free(storage->watchdog); storage->watchdog = NULL; } storage_free_now(storage); } od_rule_storage_t *od_rules_storage_ref(od_rule_storage_t *s) { atomic_fetch_add(&s->refs, 1); return s; } od_storage_endpoint_t *od_storage_find_endpoint(od_rule_storage_t *storage, const od_address_t *address) { for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_t *e = &storage->endpoints[i]; if (od_address_cmp(&e->address, address) == 0) { return e; } } return NULL; } static inline od_client_t * od_storage_watchdog_prepare_client(od_storage_watchdog_t *watchdog) { od_global_t *global = watchdog->global; od_router_t *router = global->router; od_instance_t *instance = global->instance; od_client_t *watchdog_client; watchdog_client = od_client_allocate_internal(global, "storage-watchdog"); if (watchdog_client == NULL) { od_error(&instance->logger, "watchdog", NULL, NULL, "route storage watchdog failed to allocate client"); return NULL; } watchdog_client->is_watchdog = true; watchdog_client->global = global; watchdog_client->type = OD_POOL_CLIENT_INTERNAL; od_id_generate(&watchdog_client->id, "a"); /* set storage user and database */ kiwi_var_set(&watchdog_client->startup.user, KIWI_VAR_UNDEF, watchdog->route_usr, strlen(watchdog->route_usr) + 1); kiwi_var_set(&watchdog_client->startup.database, KIWI_VAR_UNDEF, watchdog->route_db, strlen(watchdog->route_db) + 1); od_router_status_t status; status = od_router_route(router, watchdog_client); od_debug(&instance->logger, "watchdog", watchdog_client, NULL, "routing to internal wd route status: %s", od_router_status_to_str(status)); if (status != OD_ROUTER_OK) { od_error(&instance->logger, "watchdog", watchdog_client, NULL, "route storage watchdog failed: %s", od_router_status_to_str(status)); od_client_free_extended(watchdog_client); return NULL; } return watchdog_client; } static inline void od_storage_watchdog_close_client(od_storage_watchdog_t *watchdog, od_client_t *watchdog_client) { od_global_t *global = watchdog->global; od_router_t *router = global->router; od_router_close(router, watchdog_client); od_router_unroute(router, watchdog_client); od_client_free_extended(watchdog_client); } static inline od_client_t * od_storage_create_and_connect_watchdog_client(od_storage_watchdog_t *watchdog, od_storage_endpoint_t *endp) { od_global_t *global = watchdog->global; od_router_t *router = global->router; od_instance_t *instance = global->instance; od_client_t *client; client = od_storage_watchdog_prepare_client(watchdog); if (client == NULL) { od_error(&instance->logger, "watchdog", NULL, NULL, "route storage watchdog failed to prepare client"); return NULL; } od_frontend_status_t status; status = od_attach_extended_endpoint(instance, "watchdog", router, client, endp); if (status != OD_OK) { od_error(&instance->logger, "watchdog", client, NULL, "attach failed with status = %d (%s)", status, od_frontend_status_to_str(status)); od_router_unroute(router, client); od_client_free_extended(client); return NULL; } return client; } static inline void watchdog_update_endpoint(od_storage_watchdog_t *watchdog, od_storage_endpoint_t *endp) { od_global_t *global = watchdog->global; od_instance_t *instance = global->instance; od_client_t *client; client = od_storage_create_and_connect_watchdog_client(watchdog, endp); if (client == NULL) { return; } od_server_t *server = client->server; /* try to update, ignore errors - cant do nothing with it */ od_backend_update_endpoint_status(instance, client, server, "watchdog", watchdog->query, endp); od_storage_watchdog_close_client(watchdog, client); } static inline void od_storage_watchdog_do_polling_step(od_storage_watchdog_t *watchdog) { od_rule_storage_t *storage = watchdog->storage; for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_t *endp = &storage->endpoints[i]; watchdog_update_endpoint(watchdog, endp); } } static inline void od_storage_watchdog_do_polling_loop(od_storage_watchdog_t *watchdog) { od_instance_t *instance = od_global_get_instance(); od_rule_storage_t *storage = watchdog->storage; uint64_t poll_ms = storage->endpoints_status_poll_interval_ms; /* * increase the probability of using * the status obtained through watchdog */ poll_ms = poll_ms * 3 / 4; if (poll_ms == 0) { poll_ms = storage->endpoints_status_poll_interval_ms; } while (1) { od_storage_watchdog_do_polling_step(watchdog); int rc = machine_wait_flag_wait(watchdog->online, poll_ms); if (rc == 0) { od_log(&instance->logger, "watchdog", NULL, NULL, "online flag is set, exiting from watchdog for %s", watchdog->storage->name); break; } if (rc == -1 && machine_errno() != ETIMEDOUT) { od_error(&instance->logger, "watchdog", NULL, NULL, "can't wait online flag: %s (%d)", strerror(machine_errno()), machine_errno()); break; } } } void od_storage_watchdog_watch(void *arg) { od_storage_watchdog_t *watchdog = (od_storage_watchdog_t *)arg; od_global_t *global = watchdog->global; od_instance_t *instance = global->instance; od_debug(&instance->logger, "watchdog", NULL, NULL, "start watchdog for storage '%s'", watchdog->storage->name); od_storage_watchdog_do_polling_loop(watchdog); od_log(&instance->logger, "watchdog", NULL, NULL, "finishing watchdog for storage '%s'", watchdog->storage->name); od_rule_storage_t *storage = watchdog->storage; storage->watchdog = NULL; storage->watchdog_coro_id = -1; od_storage_watchdog_free(watchdog); od_rules_storage_unref(storage); } int od_storage_parse_endpoints(const char *host_str, od_storage_endpoint_t **out, size_t *count) { od_address_t *addrs = NULL; if (od_parse_addresses(host_str, &addrs, count) != OK_RESPONSE) { return NOT_OK_RESPONSE; } size_t c = *count; if (c > OD_STORAGE_MAX_ENDPOINTS) { od_free(addrs); return NOT_OK_RESPONSE; } od_storage_endpoint_t *result = od_malloc(c * sizeof(od_storage_endpoint_t)); if (result == NULL) { od_free(addrs); return NOT_OK_RESPONSE; } for (size_t i = 0; i < c; ++i) { od_address_t *result_addr = &result[i].address; od_address_init(result_addr); od_address_move(result_addr, &addrs[i]); } od_free(addrs); *out = result; /* count is set in od_parse_addresses */ return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/stream.c000066400000000000000000000532531526064202200170310ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * https://www.postgresql.org/docs/current/protocol-flow.html */ static int is_response(kiwi_be_type_t type) { /* note: Describe leads to ParametrDescription* + RowDescription | NoData */ switch (type) { case KIWI_BE_PARSE_COMPLETE: case KIWI_BE_BIND_COMPLETE: case KIWI_BE_CLOSE_COMPLETE: case KIWI_BE_COMMAND_COMPLETE: case KIWI_BE_EMPTY_QUERY_RESPONSE: case KIWI_BE_PORTAL_SUSPENDED: case KIWI_BE_ERROR_RESPONSE: case KIWI_BE_NO_DATA: case KIWI_BE_ROW_DESCRIPTION: case KIWI_BE_READY_FOR_QUERY: return 1; default: return 0; } } enum { STOP_BY_RFQ, STOP_BY_NRESPONSES }; typedef struct { int type; int rfq; int nresponse; } stream_stop_t; typedef struct { /* * accumulated message to handle * and amount of bytes of it for read completion */ machine_msg_t *msg; size_t msg_wpos; size_t msg_left; /* amount of bytes that must not be handled - just streamed */ size_t skip_left; /* stop criteria */ stream_stop_t stop; od_stream_on_responce_cb on_response; void *on_reponse_arg; } stream_t; static int server_is_fully_handled(stream_t *stream, kiwi_be_type_t type) { /* this are parsed so only full-mode handling */ switch (type) { case KIWI_BE_READY_FOR_QUERY: case KIWI_BE_ERROR_RESPONSE: case KIWI_BE_PARAMETER_STATUS: case KIWI_BE_COPY_IN_RESPONSE: case KIWI_BE_COPY_OUT_RESPONSE: case KIWI_BE_COMMAND_COMPLETE: return 1; default: break; } /* * in STOP_BY_NRESPONSES mode we need to accumulate response * terminators fully to correctly handle them and decrement counter, * otherwise a partial packet could be processed as a terminator */ if (stream->stop.type == STOP_BY_NRESPONSES) { return is_response(type); } return 0; } static inline int stream_must_stop(stream_t *stream) { stream_stop_t *stop = &stream->stop; switch (stop->type) { case STOP_BY_RFQ: return stop->rfq; case STOP_BY_NRESPONSES: return stop->rfq || stop->nresponse == 0; default: abort(); } } static void stream_init(stream_t *stream, int nresponses, od_stream_on_responce_cb on_response, void *arg) { stream->msg = NULL; stream->msg_wpos = 0; stream->msg_left = 0; stream->skip_left = 0; stream->on_response = on_response; stream->on_reponse_arg = arg; memset(&stream->stop, 0, sizeof(stream_stop_t)); if (nresponses > 0) { stream->stop.type = STOP_BY_NRESPONSES; stream->stop.nresponse = nresponses; } else { stream->stop.type = STOP_BY_RFQ; } } static void stream_msg_free(stream_t *stream) { if (stream->msg != NULL) { machine_msg_free(stream->msg); } stream->msg = NULL; stream->msg_left = 0; stream->msg_wpos = 0; } static int stream_message_in_progress(stream_t *stream) { if (stream->msg != NULL) { return 1; } if (stream->skip_left > 0) { return 1; } return 0; } static inline void handle_server_command_complete(od_client_t *client, od_server_t *server, const char *data) { /* tag is null-terminated */ const char *command_tag = data + sizeof(kiwi_header_t); if (strcmp(command_tag, "LISTEN") == 0) { od_glog("main", client, server, "LISTEN protocol detected, pin client to server"); client->backend_pin = 1; } } static od_frontend_status_t stream_handle_message(stream_t *stream, char *ctx, od_server_t *server, char *data, size_t size, int is_service, int ignore_errors) { if (od_unlikely(size < sizeof(kiwi_header_t))) { /* * this function can be called at non full message * * but can not be called on message with not enough * bytes to read header */ abort(); } int rc; od_client_t *client = server->client; od_route_t *route = server->route; od_instance_t *instance = server->global->instance; kiwi_header_t *hdr = (kiwi_header_t *)data; kiwi_be_type_t type = hdr->type; uint32_t body = kiwi_read_size(data, sizeof(kiwi_header_t)); body -= sizeof(uint32_t); size_t packet_size = sizeof(kiwi_header_t) + body; if (server_is_fully_handled(stream, type)) { if (od_unlikely(packet_size != size)) { abort(); } } if (instance->config.log_debug) { if (type == KIWI_BE_COMMAND_COMPLETE) { const char *command_tag = data + sizeof(kiwi_header_t); od_debug(&instance->logger, ctx, client, server, "%s - %s", kiwi_be_type_to_string(type), command_tag); } else { od_debug(&instance->logger, ctx, client, server, "%s", kiwi_be_type_to_string(type)); } } switch (type) { case KIWI_BE_ERROR_RESPONSE: /* we expect stream will accumulate this type of message fully */ od_backend_error(server, ctx, data, size); if (server->xproto_mode) { /* * when awaiting for exact count of responses * (on flush) meeting error means that all other * messages will be ignored until Sync */ if (stream->stop.type == STOP_BY_NRESPONSES) { /* will be decreased below to 0 */ stream->stop.nresponse = 1; } server->xproto_err = 1; } if (is_service && !ignore_errors) { return OD_ESERVER_READ; } break; case KIWI_BE_PARAMETER_STATUS: /* we expect stream will accumulate this type of message fully */ rc = od_backend_update_parameter(server, ctx, data, size, is_service); if (rc == -1) { od_gerror(ctx, client, server, "can't update parameter"); return OD_ESERVER_READ; } break; case KIWI_BE_COPY_IN_RESPONSE: server->copy_mode = 1; return OD_COPY_IN_RECEIVED; case KIWI_BE_COPY_OUT_RESPONSE: server->copy_mode = 1; if (is_service) { /* cant handle that in service streaming */ return OD_ESERVER_READ; } /* * DataRow* here until RFQ - ok */ break; case KIWI_BE_COPY_DONE: od_assert(server->copy_mode); server->copy_mode = 0; break; case KIWI_BE_COPY_BOTH_RESPONSE: /* replication must never be done by this function */ abort(); case KIWI_BE_READY_FOR_QUERY: /* we expect stream will accumulate this type of message */ stream->stop.rfq = 1; /* rfq means copy is done (written for case copy errored) */ server->copy_mode = 0; /* * reset xproto state - now can process * all new xproto messages */ server->xproto_mode = 0; server->xproto_err = 0; od_backend_ready(server, data, size); if (is_service) { break; } int64_t query_time = 0; od_stat_query_end(&route->stats, &server->stats_state, server->is_transaction, &query_time); if (instance->config.log_debug && query_time > 0) { od_log(&instance->logger, ctx, server->client, server, "query time: %" PRIi64 " microseconds", query_time); } break; case KIWI_BE_COMMAND_COMPLETE: if (is_service) { break; } if (!route->rule->pool->pin_on_listen) { break; } handle_server_command_complete(client, server, data); break; default: /* nothing to do */ break; } if (is_response(type)) { if (stream->stop.type == STOP_BY_NRESPONSES) { od_assert(stream->stop.nresponse > 0); --stream->stop.nresponse; } if (stream->on_response != NULL) { od_frontend_status_t st = stream->on_response( type, stream->on_reponse_arg); if (st != OD_OK) { return st; } } } return OD_OK; } static od_frontend_status_t stream_eat(char *ctx, stream_t *stream, od_server_t *server, int is_service, int ignore_errors, char *data, size_t size, size_t *processed) { *processed = 0; if (stream->skip_left > 0) { /* some bytes must be streamed - just account it and exit */ size_t nskip = od_min(stream->skip_left, size); stream->skip_left -= nskip; *processed = nskip; return OD_OK; } if (stream->msg_left > 0) { /* some bytes must be added to accumulated msg - lets do it */ size_t nmsg = od_min(stream->msg_left, size); stream->msg_left -= nmsg; *processed = nmsg; char *dest = machine_msg_data(stream->msg); memcpy(dest + stream->msg_wpos, data, nmsg); stream->msg_wpos += nmsg; if (stream->msg_left == 0) { /* message was accumulated fully - must handle it */ od_frontend_status_t st = stream_handle_message( stream, ctx, server, dest, machine_msg_size(stream->msg), is_service, ignore_errors); if (st != OD_OK) { return st; } stream_msg_free(stream); } return OD_OK; } /* new package */ if (size < sizeof(kiwi_header_t)) { /* package cannot be recognized - read more */ return OD_UNDEF; } kiwi_header_t *hdr = (kiwi_header_t *)data; kiwi_be_type_t type = hdr->type; uint32_t body; int rc = kiwi_validate_be_header(data, sizeof(kiwi_header_t), &body); if (rc != 0) { od_gerror(ctx, server->client, server, "invalid package header from server ('%c', size=%u)", hdr->type, hdr->len); return OD_ESERVER_READ; } body -= sizeof(uint32_t); size_t packet_size = sizeof(kiwi_header_t) + body; if (size >= packet_size) { *processed = packet_size; /* there are enough bytes to process packet */ od_frontend_status_t st = stream_handle_message(stream, ctx, server, data, packet_size, is_service, ignore_errors); if (st != OD_OK) { return st; } return OD_OK; } *processed = size; if (server_is_fully_handled(stream, type)) { /* start the accumulating */ stream->msg = machine_msg_create(packet_size); if (stream->msg == NULL) { return OD_EOOM; } char *dest = machine_msg_data(stream->msg); memcpy(dest, data, size); stream->msg_wpos = size; stream->msg_left = packet_size - size; return OD_OK; } /* no need to accumulate but need to skip some bytes in future */ stream->skip_left = packet_size - size; return OD_OK; } static void stream_destroy(stream_t *stream) { stream_msg_free(stream); } static od_frontend_status_t process_readahead(char *ctx, stream_t *stream, od_server_t *server, int is_service, int ignore_errors, uint32_t timeout_ms) { od_client_t *client = server->client; od_readahead_t *readahead = &server->io.readahead; struct iovec rvec = od_readahead_read_begin(readahead); size_t left = rvec.iov_len; char *pos = rvec.iov_base; od_frontend_status_t status = OD_OK; size_t total_processed = 0; while (!stream_must_stop(stream) && left > 0) { size_t processed = 0; status = stream_eat(ctx, stream, server, is_service, ignore_errors, pos, left, &processed); total_processed += processed; pos += processed; left -= processed; if (status == OD_UNDEF) { /* * not enough bytes - wait for more * * since it is the situation, where we can't read * packet header - its ok to just repeat read: * there must be enough place in readahead to read the * header - otherwise there is some unread packet that was * processed in previous iteration */ break; } if (status != OD_OK) { break; } if (!stream_message_in_progress(stream)) { /* * maybe client disconnected during query execution? * it is better to find it out on message end instead of * the moment at message half-read */ if (!is_service && client != NULL && !od_io_connected(&client->io)) { status = OD_ECLIENT_READ; goto to_return; } } } if (total_processed > 0 && !is_service) { size_t unused; int rc = od_io_write_raw(&client->io, rvec.iov_base, total_processed, &unused, timeout_ms, OD_IO_WRITE_DUPLEX); if (rc != 0) { status = mm_errno_get() == ENOBUFS ? OD_ECLIENT_READAHEAD_FULL : OD_ECLIENT_WRITE; } } to_return: od_readahead_read_commit(readahead, total_processed); return status; } static int readahead_empty(od_readahead_t *ra) { return od_readahead_unread(ra) == 0; } static od_frontend_status_t od_stream_server_impl(char *ctx, od_server_t *server, int is_service, int ignore_errors, int nresponses, od_stream_on_responce_cb on_response, void *arg, uint32_t timeout_ms) { od_frontend_status_t status = OD_OK; int rc; od_client_t *client = server->client; stream_t stream; stream_init(&stream, nresponses, on_response, arg); /* * set up the server io operations to be interrupted if * something happens on client io * this is done for disconnect detection */ int peering = !is_service && client != NULL; if (peering) { od_io_set_peer(&server->io, &client->io); } /* TODO: support more correct timeout */ while (!stream_must_stop(&stream)) { status = process_readahead(ctx, &stream, server, is_service, ignore_errors, timeout_ms); if (status != OD_OK && status != OD_UNDEF) { break; } if (stream_must_stop(&stream)) { break; } /* * maybe client disconnected during query execution? */ if (peering && !od_io_connected(&client->io)) { status = OD_ECLIENT_READ; break; } /* * must read only in cases of: * - readahead is fully processed * - readahead contains no enough bytes (OD_UNDEF) */ if (!readahead_empty(&server->io.readahead) && status != OD_UNDEF) { continue; } rc = od_io_read_some(&server->io, timeout_ms); if (rc == -1) { int errno_ = machine_errno(); if (errno_ == EAGAIN) { /* something happened on peer (client->io), retry */ continue; } /* timeout or any other error - give up */ status = OD_ESERVER_READ; break; } } if (peering) { od_io_remove_peer(&server->io, &client->io); } if (status == OD_ECLIENT_READ || status == OD_ECLIENT_WRITE) { /* * TODO: wait for message end here instead of just * close server connection on reset */ if (stream_message_in_progress(&stream)) { server->msg_broken = 1; } } stream_destroy(&stream); return status; } od_frontend_status_t od_stream_server_until_rfq(char *ctx, od_server_t *server, uint32_t timeout_ms) { return od_stream_server_impl(ctx, server, 0 /* is_service */, 0 /* ignore_errors */, 0 /* nresponses */, NULL, NULL, timeout_ms); } od_frontend_status_t od_service_stream_server_until_rfq(char *ctx, od_server_t *server, int ignore_errors, uint32_t timeout_ms) { return od_stream_server_impl(ctx, server, 1 /* is_service */, ignore_errors, 0 /* nresponses */, NULL, NULL, timeout_ms); } od_frontend_status_t od_stream_server_exact_completes(char *ctx, od_server_t *server, int n, od_stream_on_responce_cb on_response, void *arg, uint32_t timeout_ms) { return od_stream_server_impl(ctx, server, 0 /* is_service */, 0 /* ignore_errors */, n /* nresponses */, on_response, arg, timeout_ms); } od_frontend_status_t od_stream_server_sync(char *ctx, od_server_t *server, uint32_t timeout_ms) { return od_stream_server_impl(ctx, server, 0 /* is_service */, 0 /* ignore_errors */, 0 /* nresponses */, NULL, NULL, timeout_ms); } /* * similar to stream from server, * but no need to read packages fully * * so just proxy the bytes and track the packet headers */ typedef struct { size_t left; int done_fail_met; } copy_stream_t; static void copy_stream_init(copy_stream_t *cs) { cs->left = 0; cs->done_fail_met = 0; } static void copy_stream_destroy(copy_stream_t *cs) { (void)cs; } static od_frontend_status_t copy_handle_msg(copy_stream_t *cs, od_client_t *client, kiwi_fe_type_t type) { (void)client; switch (type) { case KIWI_FE_COPY_DONE: case KIWI_FE_COPY_FAIL: /* * we are not done yet, in case the msg is read non-fully * so mark done to be set on next package end */ cs->done_fail_met = 1; break; /* Sync and Flush messages are ignored by pg but allowed to be sent */ case KIWI_FE_SYNC: case KIWI_FE_FLUSH: case KIWI_FE_COPY_DATA: break; default: return OD_ECLIENT_PROTOCOL_ERROR; } return OD_OK; } static od_frontend_status_t copy_stream_eat(copy_stream_t *cs, char *ctx, od_client_t *client, char *data, size_t size, size_t *processed) { *processed = 0; if (cs->left > 0) { size_t nskip = od_min(cs->left, size); cs->left -= nskip; *processed = nskip; return OD_OK; } /* new package */ if (size < sizeof(kiwi_header_t)) { /* package cannot be recognized - read more */ return OD_UNDEF; } kiwi_header_t *hdr = (kiwi_header_t *)data; kiwi_fe_type_t type = hdr->type; uint32_t body; int rc = kiwi_validate_fe_header(data, sizeof(kiwi_header_t), &body); if (rc != 0) { od_gerror(ctx, client, client->server, "invalid package header from client ('%c', size=%u)", hdr->type, hdr->len); return OD_ECLIENT_PROTOCOL_ERROR; } body -= sizeof(uint32_t); size_t packet_size = sizeof(kiwi_header_t) + body; od_frontend_status_t st = copy_handle_msg(cs, client, type); if (st != OD_OK) { return st; } if (size >= packet_size) { *processed = packet_size; /* * there are enough bytes to process message * and the message already processed */ return OD_OK; } *processed = size; cs->left = packet_size - size; return OD_OK; } static int copy_stream_stop(copy_stream_t *cs) { return cs->left == 0 && cs->done_fail_met; } static od_frontend_status_t copy_process_readahead(char *ctx, copy_stream_t *stream, od_client_t *client, int stop_on_bound, uint32_t timeout_ms) { od_server_t *server = client->server; od_readahead_t *readahead = &client->io.readahead; struct iovec rvec = od_readahead_read_begin(readahead); size_t left = rvec.iov_len; char *pos = rvec.iov_base; od_frontend_status_t status = OD_OK; size_t total_processed = 0; while (!copy_stream_stop(stream) && left > 0) { size_t processed = 0; status = copy_stream_eat(stream, ctx, client, pos, left, &processed); od_assert(processed <= left); total_processed += processed; pos += processed; left -= processed; if (status == OD_UNDEF) { /* * not enough bytes - wait for more * * since it is the situation, where we can't read * packet header - its ok to just repeat read: * there must be enough place in readahead to read the * header - otherwise there is some unread packet that was * processed in previous iteration */ od_assert(processed == 0); break; } if (status != OD_OK) { od_assert(processed == 0); break; } if (stream->left == 0 && stop_on_bound) { /* * something happened on server, and this is message bound - return * * can't just stop anytime - we could sent a part of msg and to not to * break the protocol must finish message senging */ break; } } od_assert(total_processed <= rvec.iov_len); if (total_processed > 0) { size_t unused; int rc = od_io_write_raw(&server->io, rvec.iov_base, total_processed, &unused, timeout_ms, 0); od_assert((rc == 0 && (total_processed == unused)) || ((total_processed != unused) && rc != 0)); if (rc != 0) { status = OD_ESERVER_WRITE; } } od_readahead_read_commit(readahead, total_processed); return status; } static od_frontend_status_t stream_copy_from_client(char *ctx, od_client_t *client, machine_msg_t *additional, uint32_t timeout_ms) { od_frontend_status_t status = OD_OK; int rc; od_server_t *server = client->server; copy_stream_t stream; copy_stream_init(&stream); if (additional) { kiwi_header_t *hdr = (kiwi_header_t *)machine_msg_data(additional); kiwi_fe_type_t type = hdr->type; od_frontend_status_t st = copy_handle_msg(&stream, client, type); if (st != OD_OK) { return st; } rc = od_io_write(&server->io, additional, timeout_ms); if (rc != 0) { return OD_ESERVER_WRITE; } } /* * set up the client io operations to be interrupted if * something happens on server io * this is done for disconnection/errors of server detection */ od_io_set_peer(&client->io, &server->io); int server_event = 0; /* TODO: support more correct timeout */ while (!copy_stream_stop(&stream)) { /* * something happened on server, and this is message bound - return * * can't just stop anytime - we could sent a part of msg and to not to * break the protocol must finish message senging */ if (stream.left == 0 && server_event) { break; } status = copy_process_readahead(ctx, &stream, client, server_event, timeout_ms); if (status != OD_OK && status != OD_UNDEF) { break; } if (copy_stream_stop(&stream)) { break; } /* * must read only in cases of: * - readahead is fully processed * - readahead contains no enough bytes (OD_UNDEF) */ if (!readahead_empty(&client->io.readahead) && status != OD_UNDEF) { continue; } od_assert(od_readahead_unread(&client->io.readahead) < (int)sizeof(kiwi_header_t)); rc = od_io_read_some(&client->io, timeout_ms); if (rc == -1) { int errno_ = machine_errno(); if (errno_ == EAGAIN) { /* * something happened on peer (server->io) * any other events than write is a signal to stop * the streaming * * if it is write event - this is just sndbuf flush */ int ev = od_io_last_event(&server->io); if (ev & MM_R || ev & MM_ERR || ev & MM_CLOSE) { server_event = 1; } continue; } /* timeout or any other error - give up */ status = OD_ECLIENT_READ; break; } } od_io_remove_peer(&client->io, &server->io); copy_stream_destroy(&stream); return status; } od_frontend_status_t od_stream_copy_to_server(char *ctx, od_client_t *client, od_server_t *server, machine_msg_t *additional, uint32_t timeout_ms) { /* * stream from client to server all CopyData/CopyFail/CopyDone messages * then stream from server to client until RFQ */ od_frontend_status_t status = stream_copy_from_client(ctx, client, additional, timeout_ms); if (status != OD_OK) { return status; } if (server->xproto_mode) { server->copy_mode = 0; return OD_OK; } /* server->copy_mode=0 will be set on RFQ */ status = od_stream_server_until_rfq(ctx, server, timeout_ms); return status; } odyssey-1.6.0-rc1/sources/system.c000066400000000000000000000727321526064202200170650ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int od_system_send_msg(od_system_t *system, int msg_type, void *arg) { od_instance_t *instance = system->global->instance; machine_msg_t *msg = machine_msg_create(arg != NULL ? sizeof(arg) : 0); if (msg == NULL) { od_error(&instance->logger, "system", NULL, NULL, "failed to create message for system channel"); return -1; } machine_msg_set_type(msg, msg_type); if (arg) { memcpy(machine_msg_data(msg), &arg, sizeof(arg)); } machine_channel_write(system->system_channel, msg); return 0; } static void *killer(void *arg) { uint64_t timeout_ms = (uint64_t)(uintptr_t)arg; struct timespec duration; memset(&duration, 0, sizeof(duration)); duration.tv_sec = timeout_ms / 1000; duration.tv_nsec = (timeout_ms % 1000) * 1000000; struct timespec rem; memset(&rem, 0, sizeof(rem)); while (1) { int rc = nanosleep(&duration, &rem); if (rc == 0 || errno != EINTR) { break; } duration = rem; } exit(124); } static void start_timeout_thread(uint64_t timeout_ms) { pthread_t th; int rc = pthread_create(&th, NULL, killer, (void *)(uintptr_t)timeout_ms); if (rc == 0) { pthread_detach(th); } else { od_gerror("shutdown", NULL, NULL, "can't start shutdown timeout killer: %d (%s)", rc, strerror(rc)); } } void od_system_server_shutdown(od_system_server_t *server) { atomic_store(&server->closed, 1); mm_io_shutdown(server->io); mm_eventfd_write(&server->shutdown_efd, UINT32_MAX); } static int check_client_max(od_system_server_t *server, od_global_t *global, od_instance_t *instance) { /* * if global limit is reached - check if limit * for this listen is not reached */ uint64_t global_cnt = atomic_fetch_add(&global->router->clients, 1); uint64_t local_cnt = atomic_fetch_add(&server->clients, 1); if (instance->config.client_max_set && global_cnt >= (uint64_t)instance->config.client_max) { if (server->config->reserved_clients == -1) { /* no reserve on this port - give up */ goto fail; } if (local_cnt >= (uint64_t)server->config->reserved_clients) { /* reserve exhausted */ goto fail; } /* ok, we have some reserve, continue */ } else { /* global client_max is not reached - no need to check reserve */ } return 0; fail: atomic_fetch_sub(&server->clients, 1); atomic_fetch_sub(&global->router->clients, 1); return 1; } static inline void od_system_server(void *arg) { od_system_server_t *server = arg; od_instance_t *instance = server->global->instance; /* * now we are ready to accept connections on this instance * so if this odyssey was started by online restart, we need * to terminate parent odyssey instance */ od_restart_terminate_parent(); mm_eventfd_attach(&server->shutdown_efd); mm_eventfd_peer_to(&server->shutdown_efd, server->io); while (!atomic_load(&server->closed)) { od_global_t *global = server->global; /* accepted client io is not attached to epoll context yet */ mm_io_t *client_io; int rc; rc = mm_io_accept(server->io, &client_io, server->config->backlog, 0 /* attach */, 30 * 1000 /* 30 sec */); if (rc == -1) { int errno_ = machine_errno(); if (errno_ == ETIMEDOUT) { /* just no new connections for last accept interval */ continue; } if (errno_ == EAGAIN || errno_ == EINVAL) { /* * accept was interrupted by propagated signal * (which is shutdown eventfd) * * or just listen socket was shutdowned (EINVAL) * * in both cases need to stop accepting anyway */ break; } od_error(&instance->logger, "server", NULL, NULL, "accept failed: %s", mm_io_error(server->io)); if (errno_ == EADDRINUSE) { break; } continue; } /* Rate lim ourselves, if any limiter configured. * XXX: Here we do not (yet) have knowledge if client is cancel request or * not, so this rate limit actually rates everything. */ if (instance->config.accept_rate_limit != 0) { uint32_t max_wait_ms = UINT32_MAX; if (server->config->client_login_timeout > 0) { max_wait_ms = (uint32_t)server->config ->client_login_timeout; } int rc = od_rate_limiter_waitn( &global->accept_rate_limiter, 1, max_wait_ms); if (rc != 0) { mm_io_attach(client_io); od_error( &instance->logger, "server", NULL, NULL, "too many tcp connections (new connections are limited to %d per sec), connection declined", instance->config.accept_rate_limit); od_frontend_fatal_no_startup( client_io, KIWI_TOO_MANY_CONNECTIONS, "too many tcp connections (new connections are limited to %d per sec)", instance->config.accept_rate_limit); mm_io_close(client_io); mm_io_free(client_io); continue; } } if (check_client_max(server, global, instance)) { /* * writing error message could not be displayed correctly, if * client requested ssl establishment, which can not be afforded * because the system is probably under haavy load now * * so the fatal sending is just best effort */ mm_io_attach(client_io); od_error( &instance->logger, "server", NULL, NULL, "too many tcp connections (global client_max %d), connection declined", instance->config.client_max); od_frontend_fatal_no_startup( client_io, KIWI_TOO_MANY_CONNECTIONS, "too many tcp connections (global client_max %d)", instance->config.client_max); mm_io_close(client_io); mm_io_free(client_io); continue; } /* set network options */ mm_io_set_nodelay(client_io, instance->config.nodelay); if (instance->config.keepalive > 0) { mm_io_set_keepalive( client_io, 1, instance->config.keepalive, instance->config.keepalive_keep_interval, instance->config.keepalive_probes, instance->config.keepalive_usr_timeout); } if (!instance->config.disable_nolinger) { mm_io_set_nolinger(client_io); } /* allocate new client */ od_client_t *client = od_client_allocate(); if (client == NULL) { od_error(&instance->logger, "server", NULL, NULL, "failed to allocate client object"); mm_io_close(client_io); mm_io_free(client_io); continue; } od_id_generate(&client->id, "c"); /* * Only assign the io handle here; defer readahead buffer * allocation to the worker thread so it comes from the * worker's thread-local vrb cache (avoids cross-thread * cache ownership). */ client->io.io = client_io; client->rule = NULL; client->source = server; client->tls = server->tls; client->time_accept = 0; client->time_accept = machine_time_us(); /* create new client event and pass it to worker pool */ machine_msg_t *msg; msg = machine_msg_create(sizeof(od_client_t *)); if (msg == NULL) { od_error(&instance->logger, "server", NULL, NULL, "failed to allocate client message"); od_io_close(&client->io); od_client_free(client); continue; } machine_msg_set_type(msg, OD_MSG_CLIENT_NEW); memcpy(machine_msg_data(msg), &client, sizeof(od_client_t *)); rc = od_routing_slot_acquire(global, od_client_login_timeout(client)); if (rc == -1) { od_error( &instance->logger, "client_max_routing", client, NULL, "routing slot wait timed out, connection rejected (high TLS handshakes rate?)"); /* * we can send FATAL with detailed description here * but it will not work with SSL, and will be dispayed as * 'server sent an error response during SSL exchange', so * let it be more clear 'server closed the connection unexpectedly' */ machine_msg_free(msg); od_io_close(&client->io); od_client_free(client); continue; } od_worker_pool_t *worker_pool = server->global->worker_pool; od_worker_pool_feed(worker_pool, msg); } mm_eventfd_remove_peer_to(&server->shutdown_efd, server->io); mm_io_close(server->io); mm_io_free(server->io); if (!server->config->host) { /* remove unix socket files */ static OD_THREAD_LOCAL char path[PATH_MAX]; od_snprintf(path, sizeof(path), "%s/.s.PGSQL.%d", instance->config.unix_socket_dir, server->config->port); od_glog("server", NULL, NULL, "remove file %s", path); unlink(path); } } static void od_system_server_tls_dtor(void *element) { machine_tls_free(*(machine_tls_t **)element); } od_system_server_t *od_system_server_init(void) { od_system_server_t *server; server = od_malloc(sizeof(od_system_server_t)); if (server == NULL) { return NULL; } memset(server, 0, sizeof(od_system_server_t)); int rc = mm_eventfd_init(&server->shutdown_efd); if (rc != 0) { od_free(server); return NULL; } server->io = NULL; server->tls = NULL; mm_vector_init(&server->tls_retired, sizeof(machine_tls_t *), od_system_server_tls_dtor); od_id_generate(&server->sid, "sid"); atomic_init(&server->closed, false); atomic_init(&server->clients, 0); server->coro_id = -1; return server; } static inline od_retcode_t od_system_server_retire_tls(od_system_server_t *server) { if (server->tls == NULL) { return OK_RESPONSE; } if (mm_vector_append(&server->tls_retired, &server->tls) != 0) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } void od_system_server_free(od_system_server_t *server) { mm_eventfd_destroy(&server->shutdown_efd); if (server->tls) { /* Free tls */ machine_tls_free(server->tls); } mm_vector_destroy(&server->tls_retired); server->io = NULL; server->tls = NULL; od_list_unlink(&server->link); od_free(server); } static inline od_retcode_t od_system_server_start(od_system_t *system, od_config_listen_t *config, mm_addrinfo_t *addr) { od_instance_t *instance; od_system_server_t *server; instance = system->global->instance; server = od_system_server_init(); if (server == NULL) { /* failed to set up new system server */ od_error(&instance->logger, "system", NULL, NULL, "failed to allocate system server object"); return NOT_OK_RESPONSE; } server->config = config; server->addr = addr; server->global = system->global; /* create server tls */ if (server->config->tls_opts->tls_mode != OD_CONFIG_TLS_DISABLE) { server->tls = od_tls_frontend(server->config); if (server->tls == NULL) { od_error(&instance->logger, "server", NULL, NULL, "failed to create tls handler"); od_free(server); return NOT_OK_RESPONSE; } } /* create server io */ server->io = mm_io_create(); if (server->io == NULL) { od_error(&instance->logger, "server", NULL, NULL, "failed to create system io"); goto error; } char addr_name[PATH_MAX]; int addr_name_len; struct sockaddr_un saddr_un; struct sockaddr *saddr; if (server->addr) { /* resolve listen address and port */ od_getaddrname(server->addr, addr_name, sizeof(addr_name), 1, 1); addr_name_len = strlen(addr_name); saddr = server->addr->ai_addr; } else { /* set unix socket path */ memset(&saddr_un, 0, sizeof(saddr_un)); saddr_un.sun_family = AF_UNIX; saddr = (struct sockaddr *)&saddr_un; addr_name_len = od_snprintf( addr_name, sizeof(saddr_un.sun_path), "%s/.s.PGSQL.%d", instance->config.unix_socket_dir, config->port); memcpy(saddr_un.sun_path, addr_name, addr_name_len); } /* bind */ int rc; if (instance->config.bindwith_reuseport && saddr->sa_family != AF_UNIX) { rc = mm_io_bind(server->io, saddr, MM_BINDWITH_SO_REUSEPORT | MM_BINDWITH_SO_REUSEADDR); } else { rc = mm_io_bind(server->io, saddr, MM_BINDWITH_SO_REUSEADDR); } if (rc == -1) { od_error(&instance->logger, "server", NULL, NULL, "bind to '%s' failed: %s", addr_name, mm_io_error(server->io)); goto error; } /* chmod */ if (server->addr == NULL) { long mode; mode = strtol(instance->config.unix_socket_mode, NULL, 8); if ((errno == ERANGE && (mode == LONG_MAX || mode == LONG_MIN))) { od_error(&instance->logger, "server", NULL, NULL, "incorrect unix_socket_mode"); } else { rc = chmod(saddr_un.sun_path, mode); if (rc == -1) { od_error(&instance->logger, "server", NULL, NULL, "chmod(%s, %ld) failed", saddr_un.sun_path, mode); } } } od_log(&instance->logger, "server", NULL, NULL, "listening on %s", addr_name); int64_t coroutine_id; coroutine_id = machine_coroutine_create(od_system_server, server); if (coroutine_id == -1) { od_error(&instance->logger, "system", NULL, NULL, "failed to start server coroutine"); goto error; } server->coro_id = coroutine_id; /* register server in list for possible TLS reload */ od_router_t *router = system->global->router; od_list_append(&router->servers, &server->link); return OK_RESPONSE; error: if (server->tls) { machine_tls_free(server->tls); } if (server->io) { mm_io_close(server->io); mm_io_free(server->io); } od_free(server); return NOT_OK_RESPONSE; } static inline int od_system_listen(od_system_t *system) { od_instance_t *instance = system->global->instance; int binded = 0; od_list_t *i; od_list_foreach (&instance->config.listen, i) { od_config_listen_t *listen; listen = od_container_of(i, od_config_listen_t, link); /* unix socket */ int rc; if (listen->host == NULL) { rc = od_system_server_start(system, listen, NULL); if (rc == 0) { binded++; } continue; } /* listen '*' */ struct addrinfo *hints_ptr = NULL; struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; hints.ai_protocol = IPPROTO_TCP; char *host = listen->host; if (strcmp(listen->host, "*") == 0) { hints_ptr = &hints; host = NULL; } /* resolve listen address and port */ char port[16]; od_snprintf(port, sizeof(port), "%d", listen->port); mm_addrinfo_t *ai = NULL; rc = mm_getaddrinfo(host, port, hints_ptr, &ai, UINT32_MAX); if (rc != 0) { od_error(&instance->logger, "system", NULL, NULL, "failed to resolve %s:%d", listen->host, listen->port); continue; } /* listen resolved addresses */ if (host) { rc = od_system_server_start(system, listen, ai); if (rc == 0) { binded++; } mm_freeaddrinfo(ai); continue; } mm_addrinfo_t *orig = ai; while (ai) { rc = od_system_server_start(system, listen, ai); if (rc == 0) { binded++; } ai = ai->ai_next; } mm_freeaddrinfo(orig); } #ifdef ODYSSEY_VERSION_GIT od_setproctitlef( &instance->orig_argv_ptr, instance->orig_argv_ptr_len, "%s %s (git %s) listening and accepting new connections ", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT); #else od_setproctitlef(&instance->orig_argv_ptr, instance->orig_argv_ptr_len, "%s %s listening and accepting new connections ", ODYSSEY_NAME, ODYSSEY_VERSION_NUMBER); #endif return binded; } static inline int od_config_listen_host_cmp(char *host_listen, char *host_server) { if (host_listen == NULL && host_server == NULL) { return 0; } if (host_listen == NULL || host_server == NULL) { return 1; } return strcmp(host_listen, host_server); } void od_system_config_reload(od_system_t *system) { od_instance_t *instance = system->global->instance; od_router_t *router = system->global->router; od_hba_t *hba = system->global->hba; od_list_t *i; od_log(&instance->logger, "config", NULL, NULL, "importing changes from '%s'", instance->config_file); od_rules_lock(&router->rules); od_rules_stop_checkers(&router->rules); od_config_t config; od_config_init(&config); od_rules_t rules; od_rules_init(&rules); od_hba_rules_t hba_rules; od_hba_rules_init(&hba_rules); int rc; rc = od_cfg_import(&instance->logger, &config, &rules, system->global, &hba_rules, instance->config_file); if (rc == -1) { goto error; } rc = od_config_validate(&config, &instance->logger); if (rc == -1) { goto error; } rc = od_rules_validate(&rules, &config, &instance->logger); if (rc == -1) { goto error; } od_config_reload(&instance->config, &config); od_logger_set_debug(&instance->logger, instance->config.log_debug); od_hba_reload(hba, &hba_rules); /* auto-generate default rule for auth_query if none specified */ rc = od_rules_autogenerate_defaults(&rules, &instance->logger); od_rules_sort_for_matching(&rules); if (rc == -1) { goto error; } /* * Merge storages: reuse unchanged storages (keeping their watchdogs * and endpoint statuses alive), move new/changed storages into * router->rules, and unref removed/changed ones. Running * watchdogs keep their storages alive via refcount and stop * automatically when the last non-watchdog ref is dropped. After * this, rules in the freshly parsed config reference the correct * storages, so od_rules_merge can compare origin->storage and * rule->storage by pointer. */ od_rules_storage_merge(&router->rules, &rules); od_rules_unlock(&router->rules); machine_tls_cache_invalidate(); /* Reload TLS certificates */ od_list_foreach (&router->servers, i) { od_system_server_t *server; od_config_listen_t *listen_config = NULL; server = od_container_of(i, od_system_server_t, link); od_list_t *j; od_list_foreach (&config.listen, j) { listen_config = od_container_of(j, od_config_listen_t, link); if (listen_config->port == server->config->port && od_config_listen_host_cmp(listen_config->host, server->config->host) == 0) { /* we have found matched listen config rule */ break; } listen_config = NULL; } char *host_name = od_config_listen_host_name(server->config); if (listen_config == NULL) { od_log(&instance->logger, "reload-config", NULL, NULL, "failed to match listen config for %s:%d", host_name, server->config->port); } else if (server->config->tls_opts->tls_mode != listen_config->tls_opts->tls_mode) { od_log(&instance->logger, "reload-config", NULL, NULL, "reloaded tls mode for %s:%d", host_name, server->config->port); server->config->tls_opts->tls_mode = listen_config->tls_opts->tls_mode; } /* build tls from the new config, so that changed cert paths apply */ od_config_listen_t *tls_source = listen_config != NULL ? listen_config : server->config; if (tls_source->tls_opts->tls_mode == OD_CONFIG_TLS_DISABLE) { continue; } int files_changed = listen_config != NULL && !od_tls_opts_files_eq(server->config->tls_opts, listen_config->tls_opts); /* do not let a broken certificate replace a working one */ char tls_error[256]; if (od_tls_frontend_validate(tls_source, tls_error, sizeof(tls_error)) != OK_RESPONSE) { od_error( &instance->logger, "reload-config", NULL, NULL, "failed to load tls certificate for %s:%d, keeping previous certificate: %s", host_name, server->config->port, tls_error); continue; } machine_tls_t *tls = od_tls_frontend(tls_source); if (tls == NULL) { od_error( &instance->logger, "reload-config", NULL, NULL, "failed to build tls handler for %s:%d, keeping previous certificate", host_name, server->config->port); continue; } /* * The replaced handle stays alive until shutdown: clients * accepted earlier still point at it. */ if (od_system_server_retire_tls(server) != OK_RESPONSE) { machine_tls_free(tls); od_error( &instance->logger, "reload-config", NULL, NULL, "failed to retire tls handler for %s:%d, keeping previous certificate", host_name, server->config->port); continue; } server->tls = tls; if (files_changed) { od_log(&instance->logger, "reload-config", NULL, NULL, "reloaded tls certificate files for %s:%d", host_name, server->config->port); } } od_config_free(&config); if (instance->config.log_config) { od_rules_print(&rules, &instance->logger); } /* Merge configuration changes. * * Add new routes or obsolete previous ones which are updated or not * present in new config file. * * Force obsolete clients to disconnect. */ od_log(&instance->logger, "rules", NULL, NULL, "reconfigure rules"); int updates = od_router_reconfigure(router, &rules); /* start watchdogs and group checkers for new/changed storages * and rules — directly in system thread scheduler */ od_rules_lock(&router->rules); od_rules_storages_watchdogs_run(&instance->logger, &router->rules); od_rules_unlock(&router->rules); od_router_lock(router); od_rules_groups_checkers_run(&instance->logger, &router->rules); od_router_unlock(router); /* free unused rules */ od_rules_free(&rules); od_log(&instance->logger, "rules", NULL, NULL, "%d routes created/deleted and scheduled for removal", updates); /* the file on disk loaded, so a restart would come up on it */ atomic_store(&instance->config_load_failed, 0); return; error: /* * Keep running on the config already in memory. The file on disk is * not usable, so a restart would not come up on it. */ atomic_store(&instance->config_load_failed, 1); od_rules_unlock(&router->rules); od_config_free(&config); od_rules_free(&rules); od_error(&instance->logger, "reload-config", NULL, NULL, "failed to load '%s', keeping the running configuration", instance->config_file); } static inline void od_system(void *arg) { od_system_t *system = arg; od_global_t *global = system->global; od_instance_t *instance = system->global->instance; od_router_t *router = system->global->router; system->system_channel = machine_channel_create(); if (system->system_channel == NULL) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to create system channel"); return; } instance->pstmts = od_global_pstmts_map_create( 4 * (size_t)instance->config.workers); if (instance->pstmts == NULL) { od_error(&instance->logger, "system", NULL, NULL, "failed to create pstmts map, errno = %d (%s)", machine_errno(), strerror(machine_errno())); return; } uint64_t max_inflight = instance->config.cancel_max_inflight > 0 ? (uint64_t)instance->config.cancel_max_inflight : 2 * (uint64_t)instance->config.workers; mm_sem_init(&global->cancel_sem, max_inflight); mm_sem_init(&global->routing_sem, (uint64_t)instance->config.client_max_routing); od_rate_limiter_init(&global->cancel_rate_limiter, (uint64_t)instance->config.cancel_rate_limit); od_rate_limiter_init(&global->accept_rate_limiter, (uint64_t)instance->config.accept_rate_limit); /* start worker threads */ int rc; #ifdef LDAP_FOUND rc = od_ldap_workers_init(instance->config.workers); if (rc == -1) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to start ldap pool, errno = %d (%s)", machine_errno(), strerror(machine_errno())); return; } #endif /* start worker threads */ od_worker_pool_t *worker_pool = system->global->worker_pool; rc = od_worker_pool_start(worker_pool, system->global, (uint32_t)instance->config.workers); if (rc == -1) { return; } /* start signal handler coroutine */ int64_t mid; mid = machine_create("sighandler", od_system_signal_handler, system); if (mid == -1) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to start signal handler"); return; } system->sighandler_machine = mid; /* start listen servers */ rc = od_system_listen(system); if (rc == 0) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to bind any listen address, errno=%d (%s)", mm_errno_get(), strerror(mm_errno_get())); exit(1); } od_cron_start(global->cron, global); od_rules_storages_watchdogs_run(&instance->logger, &router->rules); od_rules_groups_checkers_run(&instance->logger, &router->rules); /* event loop: handle reload and shutdown requests */ bool shutdown = false; while (!shutdown) { machine_msg_t *msg = machine_channel_read( system->system_channel, UINT32_MAX); if (msg == NULL) { od_log(&instance->logger, "system", NULL, NULL, "NULL message in system channel"); break; } int type = machine_msg_type(msg); void *msg_arg = NULL; if (machine_msg_size(msg) >= (int)sizeof(msg_arg)) { memcpy(&msg_arg, machine_msg_data(msg), sizeof(msg_arg)); } machine_msg_free(msg); switch (type) { case OD_MSG_RELOAD: od_system_config_reload(system); if (msg_arg != NULL) { mm_wait_flag_set((mm_wait_flag_t *)msg_arg); } break; case OD_MSG_GC: od_log(&instance->logger, "system", NULL, NULL, "GC request"); od_cron_expire_now(global->cron); if (msg_arg != NULL) { mm_wait_flag_set((mm_wait_flag_t *)msg_arg); } break; case OD_MSG_SHUTDOWN: od_log(&instance->logger, "system", NULL, NULL, "shutdown request"); shutdown = true; break; default: od_log(&instance->logger, "system", NULL, NULL, "unexpected message type %d in system channel", type); break; } } if (instance->config.graceful_shutdown_timeout_ms != 0) { start_timeout_thread( instance->config.graceful_shutdown_timeout_ms); } od_list_t *i, *n; od_list_foreach_safe (&router->servers, i, n) { od_system_server_t *server; server = od_container_of(i, od_system_server_t, link); od_system_server_shutdown(server); } od_rules_stop_checkers(&router->rules); od_cron_stop(global->cron); /* stop workers first so client/server coroutines release * their storage refs — otherwise od_rules_cleanup won't * reach r==2 and watchdogs won't get set_offline */ od_worker_pool_shutdown(worker_pool); od_worker_pool_wait_gracefully_shutdown(worker_pool); machine_wait_nb(system->sighandler_machine); od_list_foreach_safe (&router->servers, i, n) { od_system_server_t *server; server = od_container_of(i, od_system_server_t, link); machine_join(server->coro_id); } /* collect watchdog coroutine IDs before rules are freed */ int watchdog_count = 0; { od_list_t *j; od_list_foreach (&router->rules.storages, j) { od_rule_storage_t *s; s = od_container_of(j, od_rule_storage_t, link); if (s->watchdog_coro_id != -1) { watchdog_count++; } } } int64_t *watchdog_ids = NULL; if (watchdog_count > 0) { watchdog_ids = od_malloc(sizeof(int64_t) * watchdog_count); if (watchdog_ids) { int k = 0; od_list_t *j; od_list_foreach (&router->rules.storages, j) { od_rule_storage_t *s; s = od_container_of(j, od_rule_storage_t, link); if (s->watchdog_coro_id != -1) { watchdog_ids[k++] = s->watchdog_coro_id; } } } } /* signal all watchdogs to stop before dropping storage refs. * * od_rules_storage_unref no longer signals set_offline via * refcount — the caller must do it explicitly. Here we * signal every running watchdog, then join it (below) so it * exits and drops its own storage ref before od_router_free * frees the routes and rules. */ { od_list_t *j; od_list_foreach (&router->rules.storages, j) { od_rule_storage_t *s; s = od_container_of(j, od_rule_storage_t, link); if (s->watchdog != NULL && s->watchdog_coro_id != -1) { od_storage_watchdog_set_offline(s->watchdog); } } } /* unref storages: drop the storage list refs. Rules and routes * are still alive, so storage refcount stays above 1. The * storage will be freed later when od_router_free drops route * rule refs and the watchdog's own ref is already gone. */ od_rules_cleanup(&router->rules); /* join watchdog coroutines: machine_join yields to scheduler, * giving each watchdog a chance to see set_offline (signalled * above) and exit. Routes and rules are still alive, so the * watchdog can safely access them during its final polling step. */ if (watchdog_ids) { for (int k = 0; k < watchdog_count; k++) { machine_join(watchdog_ids[k]); } od_free(watchdog_ids); } /* now safe to free router: all watchdogs are dead, so no one * will access routes or rules after this point */ od_router_free(router); od_soft_oom_stop_checker(&global->soft_oom); #ifdef LDAP_FOUND od_ldap_workers_destroy(); #endif if (instance->config.host_watcher_enabled) { od_host_watcher_destroy(&global->host_watcher); } if (instance->config.hba_file != NULL) { od_hba_free(global->hba); } od_extension_free(&instance->logger, global->extensions); od_logger_shutdown(&instance->logger); od_logger_wait_finish(&instance->logger); mm_sem_destroy(&global->cancel_sem); mm_sem_destroy(&global->routing_sem); machine_channel_free(system->system_channel); od_instance_free(instance); od_global_destroy(global); od_system_free(system); } void od_system_init(od_system_t *system) { system->machine = -1; system->sighandler_machine = -1; system->system_channel = NULL; system->global = NULL; } int od_system_start(od_system_t *system, od_global_t *global) { system->global = global; od_instance_t *instance = global->instance; system->machine = machine_create("system", od_system, system); if (system->machine == -1) { od_error(&instance->logger, "system", NULL, NULL, "failed to create system thread"); return -1; } return 0; } od_system_t *od_system_create(void) { od_system_t *s = od_malloc(sizeof(od_system_t)); if (s == NULL) { return NULL; } od_system_init(s); return s; } void od_system_free(od_system_t *system) { od_free(system); } odyssey-1.6.0-rc1/sources/systemd_notify.c000066400000000000000000000013351526064202200206100ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #ifdef HAVE_SYSTEMD #include void od_systemd_notify_ready(void) { sd_notify(0, "READY=1\n" "STATUS=Ready to accept connections"); } void od_systemd_notify_reloading(const char *msg) { char buff[256]; snprintf(buff, sizeof(buff), "RELOADING=1\nSTATUS=%s", msg); sd_notify(0, buff); } void od_systemd_notify_stopping(void) { sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down gracefully"); } void od_systemd_notify_mainpid(pid_t pid) { char msg[64]; snprintf(msg, sizeof(msg), "MAINPID=%d", (int)pid); sd_notify(0, msg); } #endif /* HAVE_SYSTEMD */ odyssey-1.6.0-rc1/sources/systemd_notify.h000066400000000000000000000011451526064202200206140ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #ifdef HAVE_SYSTEMD void od_systemd_notify_ready(void); void od_systemd_notify_reloading(const char *msg); void od_systemd_notify_stopping(void); void od_systemd_notify_mainpid(pid_t pid); #else /* No-op stubs when systemd is not available */ static inline void od_systemd_notify_ready(void) { } static inline void od_systemd_notify_reloading(const char *msg) { (void)msg; } static inline void od_systemd_notify_stopping(void) { } static inline void od_systemd_notify_mainpid(pid_t pid) { (void)pid; } #endif /* HAVE_SYSTEMD */ odyssey-1.6.0-rc1/sources/tdigest.c000066400000000000000000000205251526064202200171750ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include typedef struct node { double mean; double count; } node_t; struct td_histogram { /* compression is a setting used to configure the size of centroids when */ /* merged. */ double compression; /* cap is the total size of nodes */ int cap; /* merged_nodes is the number of merged nodes at the front of nodes. */ int merged_nodes; /* unmerged_nodes is the number of buffered nodes. */ int unmerged_nodes; double merged_count; double unmerged_count; mm_sleeplock_t lock; node_t nodes[FLEXIBLE_ARRAY_MEMBER]; /* ISO C99 flexible array member */ }; static bool is_very_small(double val) { return !(val > .000000001 || val < -.000000001); } static int cap_from_compression(double compression) { return (6 * (int)(compression)) + 10; } static bool should_merge(td_histogram_t *h) { return ((h->merged_nodes + h->unmerged_nodes) == h->cap); } static int next_node(td_histogram_t *h) { return h->merged_nodes + h->unmerged_nodes; } static void merge(td_histogram_t *h); /*////////////////////////////////////////////////////////////////////////////// */ /* Constructors */ /*////////////////////////////////////////////////////////////////////////////// */ static size_t td_required_buf_size(double compression) { return sizeof(td_histogram_t) + (cap_from_compression(compression) * sizeof(node_t)); } /* td_init will initialize a td_histogram_t inside buf which is buf_size bytes. */ /* If buf_size is too small (smaller than compression + 1) or buf is NULL, */ /* the returned pointer will be NULL. */ /* */ /* In general use td_required_buf_size to figure out what size buffer to */ /* pass. */ static td_histogram_t *td_init(double compression, size_t buf_size, char *buf) { td_histogram_t *h = (td_histogram_t *)(buf); if (!h) { return NULL; } bzero((void *)(h), buf_size); *h = (td_histogram_t){ .compression = compression, .cap = (buf_size - sizeof(td_histogram_t)) / sizeof(node_t), .merged_nodes = 0, .merged_count = 0, .unmerged_nodes = 0, .unmerged_count = 0, }; return h; } td_histogram_t *td_new(double compression) { size_t memsize = td_required_buf_size(compression); return td_init(compression, memsize, (char *)(od_malloc(memsize))); } void td_safe_free(td_histogram_t *h) { if (h != NULL) { td_free(h); } } void td_copy(td_histogram_t *dst, td_histogram_t *src) { od_assert(dst->compression == src->compression); memcpy(dst, src, td_required_buf_size(src->compression)); } void td_free(td_histogram_t *h) { od_free((void *)(h)); } void td_merge(td_histogram_t *into, td_histogram_t *from) { merge(into); merge(from); for (int i = 0; i < from->merged_nodes; i++) { node_t *n = &from->nodes[i]; td_add(into, n->mean, n->count); } } void td_reset(td_histogram_t *h) { mm_sleeplock_lock(&h->lock); { bzero((void *)(&h->nodes[0]), sizeof(node_t) * h->cap); h->merged_nodes = 0; h->merged_count = 0; h->unmerged_nodes = 0; h->unmerged_count = 0; } mm_sleeplock_unlock(&h->lock); } void td_decay(td_histogram_t *h, double factor) { merge(h); h->unmerged_count *= factor; h->merged_count *= factor; for (int i = 0; i < h->merged_nodes; i++) { h->nodes[i].count *= factor; } } double td_total_count(td_histogram_t *h) { return h->merged_count + h->unmerged_count; } double td_total_sum(td_histogram_t *h) { node_t *n = NULL; double sum = 0; int total_nodes = h->merged_nodes + h->unmerged_nodes; for (int i = 0; i < total_nodes; i++) { n = &h->nodes[i]; sum += n->mean * n->count; } return sum; } double td_quantile_of(td_histogram_t *h, double val) { merge(h); if (h->merged_nodes == 0) { return NAN; } double k = 0; int i = 0; node_t *n = NULL; for (i = 0; i < h->merged_nodes; i++) { n = &h->nodes[i]; if (n->mean >= val) { break; } k += n->count; } if (val == n->mean) { /* technically this needs to find all of the nodes which contain this */ /* value and sum their weight */ double count_at_value = n->count; for (i += 1; i < h->merged_nodes && h->nodes[i].mean == n->mean; i++) { count_at_value += h->nodes[i].count; } return (k + (count_at_value / 2)) / h->merged_count; } else if (val > n->mean) { /* past the largest */ return 1; } else if (i == 0) { return 0; } /* we want to figure out where along the line from the prev node to this */ /* node, the value falls */ node_t *nr = n; node_t *nl = n - 1; k -= (nl->count / 2); /* we say that at zero we're at nl->mean */ /* and at (nl->count/2 + nr->count/2) we're at nr */ double m = (nr->mean - nl->mean) / (nl->count / 2 + nr->count / 2); double x = (val - nl->mean) / m; return (k + x) / h->merged_count; } double td_value_at(td_histogram_t *h, double q) { merge(h); if (q < 0 || q > 1 || h->merged_nodes == 0) { return NAN; } /* if left of the first node, use the first node */ /* if right of the last node, use the last node, use it */ double goal = q * h->merged_count; double k = 0; int i = 0; node_t *n = NULL; for (i = 0; i < h->merged_nodes; i++) { n = &h->nodes[i]; if (k + n->count > goal) { break; } k += n->count; } double delta_k = goal - k - (n->count / 2); if (is_very_small(delta_k)) { return n->mean; } bool right = delta_k > 0; if ((right && ((i + 1) == h->merged_nodes)) || (!right && (i == 0))) { return n->mean; } node_t *nl; node_t *nr; if (right) { nl = n; nr = &h->nodes[i + 1]; k += (nl->count / 2); } else { nl = &h->nodes[i - 1]; nr = n; k -= (nl->count / 2); } double x = goal - k; /* we have two points (0, nl->mean), (nr->count, nr->mean) */ /* and we want x */ double m = (nr->mean - nl->mean) / (nl->count / 2 + nr->count / 2); return m * x + nl->mean; } double td_trimmed_mean(td_histogram_t *h, double lo, double hi) { if (should_merge(h)) { merge(h); } double total_count = h->merged_count; double left_tail_count = lo * total_count; double right_tail_count = hi * total_count; double count_seen = 0; double weighted_mean = 0; for (int i = 0; i < h->merged_nodes; i++) { if (i > 0) { count_seen += h->nodes[i - 1].count; } node_t *n = &h->nodes[i]; if (n->count < left_tail_count) { continue; } if (count_seen > right_tail_count) { break; } double left = count_seen; if (left < left_tail_count) { left = left_tail_count; } double right = count_seen + n->count; if (right > right_tail_count) { right = right_tail_count; } weighted_mean += n->mean * (right - left); } double included_count = total_count * (hi - lo); return weighted_mean / included_count; } void td_add(td_histogram_t *h, double mean, double count) { mm_sleeplock_lock(&h->lock); if (should_merge(h)) { merge(h); } h->nodes[next_node(h)] = (node_t){ .mean = mean, .count = count, }; h->unmerged_nodes++; h->unmerged_count += count; mm_sleeplock_unlock(&h->lock); } static int compare_nodes(const void *v1, const void *v2) { node_t *n1 = (node_t *)(v1); node_t *n2 = (node_t *)(v2); if (n1->mean < n2->mean) { return -1; } else if (n1->mean > n2->mean) { return 1; } else { return 0; } } static void merge(td_histogram_t *h) { if (h->unmerged_nodes == 0) { return; } int N = h->merged_nodes + h->unmerged_nodes; qsort((void *)(h->nodes), N, sizeof(node_t), &compare_nodes); double total_count = h->merged_count + h->unmerged_count; double denom = 2 * M_PI * total_count * log(total_count); double normalizer = h->compression / denom; int cur = 0; double count_so_far = 0; for (int i = 1; i < N; i++) { double proposed_count = h->nodes[cur].count + h->nodes[i].count; double z = proposed_count * normalizer; double q0 = count_so_far / total_count; double q2 = (count_so_far + proposed_count) / total_count; bool should_add = (z <= (q0 * (1 - q0))) && (z <= (q2 * (1 - q2))); if (should_add) { h->nodes[cur].count += h->nodes[i].count; double delta = h->nodes[i].mean - h->nodes[cur].mean; double weighted_delta = (delta * h->nodes[i].count) / h->nodes[cur].count; h->nodes[cur].mean += weighted_delta; } else { count_so_far += h->nodes[cur].count; cur++; h->nodes[cur] = h->nodes[i]; } if (cur != i) { h->nodes[i] = (node_t){ .mean = 0, .count = 0, }; } } h->merged_nodes = cur + 1; h->merged_count = total_count; h->unmerged_nodes = 0; h->unmerged_count = 0; } odyssey-1.6.0-rc1/sources/tests/000077500000000000000000000000001526064202200165245ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/tests/README.md000066400000000000000000000003521526064202200200030ustar00rootroot00000000000000### Odyssey test suite #### Build and run instructions Before you continue reading, please see and follow [odyssey build instructions](../README.md#Build-instructions). To run tests: ```sh cd test ./odyssey_test ``` odyssey-1.6.0-rc1/sources/tests/kiwi/000077500000000000000000000000001526064202200174675ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/tests/kiwi/test_kiwi_be_read_query.c000066400000000000000000000071621526064202200245310ustar00rootroot00000000000000/* * Test for kiwi_be_read_query and kiwi_be_read_parse_dest payload * validation. * * A Query ('Q') message must carry a NUL-terminated query string and * a Parse ('P') message must carry a NUL-terminated query inside its * description. Readers must reject messages without the terminator, * otherwise strlen-based callers read past the end of the message * buffer (heap-buffer-overflow read). */ #include #include #include #include /* * Helper to build a 'Q' message with exact-size heap allocation, so * out-of-bounds reads are caught by ASAN. * Format: 'Q' (1 byte) + length (4 bytes, big-endian) + payload */ static char *build_query_message(const char *payload, size_t payload_len, uint32_t *total_len) { char *buf = malloc(1 + 4 + payload_len); test(buf != NULL); buf[0] = KIWI_FE_QUERY; uint32_t len = 4 + payload_len; buf[1] = (len >> 24) & 0xFF; buf[2] = (len >> 16) & 0xFF; buf[3] = (len >> 8) & 0xFF; buf[4] = len & 0xFF; memcpy(buf + 5, payload, payload_len); *total_len = 1 + 4 + payload_len; return buf; } /* * Helper to build a 'P' message: * 'P' (1 byte) + length (4 bytes) + name\0 + query + int16 param count */ static char *build_parse_message(const char *query, size_t query_len, uint32_t *total_len) { size_t payload_len = 2 + query_len + 2; char *buf = malloc(1 + 4 + payload_len); test(buf != NULL); buf[0] = KIWI_FE_PARSE; uint32_t len = 4 + payload_len; buf[1] = (len >> 24) & 0xFF; buf[2] = (len >> 16) & 0xFF; buf[3] = (len >> 8) & 0xFF; buf[4] = len & 0xFF; /* operator_name: "s\0" */ buf[5] = 's'; buf[6] = '\0'; memcpy(buf + 7, query, query_len); /* int16 parameter type count: 0 */ buf[7 + query_len] = 0; buf[8 + query_len] = 0; *total_len = 1 + 4 + payload_len; return buf; } static void test_query_valid(void) { uint32_t total_len; char *buf = build_query_message("SELECT 1\0", 9, &total_len); char *query = NULL; uint32_t query_len = 0; int rc = kiwi_be_read_query(buf, total_len, &query, &query_len); test(rc == 0); test(query == buf + 5); test(query_len == 9); test(strcmp(query, "SELECT 1") == 0); free(buf); } static void test_query_missing_nul(void) { uint32_t total_len; /* no trailing NUL byte */ char *buf = build_query_message("SELECT 1", 8, &total_len); char *query = NULL; uint32_t query_len = 0; int rc = kiwi_be_read_query(buf, total_len, &query, &query_len); test(rc == -1); free(buf); } static void test_query_empty_payload(void) { uint32_t total_len; /* zero-length payload, no space for the terminator at all */ char *buf = build_query_message("", 0, &total_len); char *query = NULL; uint32_t query_len = 0; int rc = kiwi_be_read_query(buf, total_len, &query, &query_len); test(rc == -1); free(buf); } static void test_parse_dest_valid(void) { uint32_t total_len; char *buf = build_parse_message("SELECT 1\0", 9, &total_len); kiwi_prepared_statement_t dest; int rc = kiwi_be_read_parse_dest(buf, total_len, &dest); test(rc == 0); test(dest.operator_name_len == 2); test(strncmp(dest.operator_name, "s", 1) == 0); test(strncmp(dest.description, "SELECT 1", 8) == 0); free(buf); } static void test_parse_dest_missing_nul(void) { uint32_t total_len; /* query without the trailing NUL inside the description */ char *buf = build_parse_message("SELECT 1", 8, &total_len); kiwi_prepared_statement_t dest; int rc = kiwi_be_read_parse_dest(buf, total_len, &dest); test(rc == -1); free(buf); } void kiwi_test_be_read_query(void) { test_query_valid(); test_query_missing_nul(); test_query_empty_payload(); test_parse_dest_valid(); test_parse_dest_missing_nul(); } odyssey-1.6.0-rc1/sources/tests/kiwi/test_kiwi_enquote.c000066400000000000000000000020151526064202200233730ustar00rootroot00000000000000#include #include void test_without_screen(void) { char *simple_string = "abcdefg123098adad[]"; size_t sz = 25; char dst[sz]; int res; res = kiwi_enquote(simple_string, dst, sz); test(res != -1); test(strcmp(dst, "E\'abcdefg123098adad[]\'") == 0) } void test_one_screen(void) { char *string = "root\\bin"; size_t sz = 15; char dst[sz]; int res; res = kiwi_enquote(string, dst, sz); test(res != -1); test(strcmp(dst, "E\'root\\\\bin\'") == 0) } void test_many_screens(void) { char *string = "\\root\\bin\\\'easy\'win\'"; size_t sz = 100; char dst[sz]; int res; res = kiwi_enquote(string, dst, sz); test(res != -1); test(strcmp(dst, "E\'\\\\root\\\\bin\\\\\'\'easy\'\'win\'\'\'") == 0) } void test_invalid(void) { char *string = "\\root\\bin\\\'easy\'win\'"; size_t sz = 10; char dst[sz]; int res; res = kiwi_enquote(string, dst, sz); test(res == -1); } void kiwi_test_enquote(void) { test_without_screen(); test_one_screen(); test_many_screens(); test_invalid(); } odyssey-1.6.0-rc1/sources/tests/kiwi/test_kiwi_fe_read_auth.c000066400000000000000000000142131526064202200243240ustar00rootroot00000000000000/* * Test for kiwi_fe_read_auth SASL mechanism list parsing. * * PostgreSQL sends a list of SASL mechanisms as null-terminated strings * in the AuthenticationSASL message. When TLS is enabled, PostgreSQL * sends: ["SCRAM-SHA-256-PLUS", "SCRAM-SHA-256", ""]. * * This test verifies that the parser correctly handles: * 1. Single mechanism (SCRAM-SHA-256 only) * 2. Multiple mechanisms with SCRAM-SHA-256 first * 3. Multiple mechanisms with SCRAM-SHA-256-PLUS first (TLS case) * 4. Unsupported mechanisms only */ #include #include /* * Helper to build an AuthenticationSASL message. * Format: 'R' (1 byte) + length (4 bytes, big-endian) + type (4 bytes) + mechanisms */ static void build_auth_sasl_message(char *buf, uint32_t *total_len, const char *mechanisms, size_t mech_len) { /* Message type */ buf[0] = KIWI_BE_AUTHENTICATION; /* Length = 4 (length itself) + 4 (auth type) + mechanism data */ uint32_t len = 4 + 4 + mech_len; buf[1] = (len >> 24) & 0xFF; buf[2] = (len >> 16) & 0xFF; buf[3] = (len >> 8) & 0xFF; buf[4] = len & 0xFF; /* Auth type = 10 (AuthenticationSASL) */ buf[5] = 0; buf[6] = 0; buf[7] = 0; buf[8] = 10; /* Mechanism list */ memcpy(buf + 9, mechanisms, mech_len); *total_len = 1 + 4 + 4 + mech_len; } /* Test: Single SCRAM-SHA-256 mechanism (should succeed) */ void test_sasl_single_scram_sha_256(void) { char buf[128]; uint32_t total_len; /* "SCRAM-SHA-256\0" */ const char mechanisms[] = "SCRAM-SHA-256"; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == 0); test(type == 10); } /* Test: Multiple mechanisms with SCRAM-SHA-256 first (should succeed) */ void test_sasl_scram_sha_256_first(void) { char buf[128]; uint32_t total_len; /* "SCRAM-SHA-256\0SCRAM-SHA-256-PLUS\0\0" */ const char mechanisms[] = "SCRAM-SHA-256\0SCRAM-SHA-256-PLUS\0"; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == 0); test(type == 10); } /* Test: SCRAM-SHA-256-PLUS first, then SCRAM-SHA-256 (TLS case - should succeed) */ void test_sasl_scram_sha_256_plus_first(void) { char buf[128]; uint32_t total_len; /* "SCRAM-SHA-256-PLUS\0SCRAM-SHA-256\0\0" - this is what PostgreSQL sends with TLS */ const char mechanisms[] = "SCRAM-SHA-256-PLUS\0SCRAM-SHA-256\0"; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == 0); test(type == 10); } /* Test: Only unsupported mechanisms (should fail) */ void test_sasl_unsupported_only(void) { char buf[128]; uint32_t total_len; /* "SCRAM-SHA-512\0UNKNOWN\0\0" - no supported mechanism */ const char mechanisms[] = "SCRAM-SHA-512\0UNKNOWN\0"; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == -1); } /* Test: SCRAM-SHA-256-PLUS only without SCRAM-SHA-256 (should fail) */ void test_sasl_scram_sha_256_plus_only(void) { char buf[128]; uint32_t total_len; /* "SCRAM-SHA-256-PLUS\0\0" - channel binding only, no plain SCRAM */ const char mechanisms[] = "SCRAM-SHA-256-PLUS\0"; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == -1); } /* Test: Empty mechanism list (should fail) */ void test_sasl_empty_list(void) { char buf[128]; uint32_t total_len; /* "\0" - empty list */ const char mechanisms[] = ""; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == -1); } /* Test: SCRAM-SHA-256 as third mechanism (should succeed) */ void test_sasl_scram_sha_256_third(void) { char buf[128]; uint32_t total_len; /* "SCRAM-SHA-256-PLUS\0SCRAM-SHA-512\0SCRAM-SHA-256\0\0" */ const char mechanisms[] = "SCRAM-SHA-256-PLUS\0SCRAM-SHA-512\0SCRAM-SHA-256\0"; build_auth_sasl_message(buf, &total_len, mechanisms, sizeof(mechanisms)); uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == 0); test(type == 10); } /* Test: Malformed packet - no null terminator within bounds (should fail safely) */ void test_sasl_malformed_no_null_terminator(void) { char buf[128]; /* Build a malformed message manually - mechanism string without null */ buf[0] = KIWI_BE_AUTHENTICATION; /* Length = 4 + 4 + 10 (no null terminator in mechanism) */ uint32_t len = 4 + 4 + 10; buf[1] = (len >> 24) & 0xFF; buf[2] = (len >> 16) & 0xFF; buf[3] = (len >> 8) & 0xFF; buf[4] = len & 0xFF; /* Auth type = 10 */ buf[5] = 0; buf[6] = 0; buf[7] = 0; buf[8] = 10; /* Mechanism data without null terminator - just fill with 'A' */ memset(buf + 9, 'A', 10); uint32_t total_len = 1 + 4 + 4 + 10; uint32_t type; char salt[4]; char *auth_data; size_t auth_data_size; /* Should fail safely without reading past buffer */ int rc = kiwi_fe_read_auth(buf, total_len, &type, salt, &auth_data, &auth_data_size); test(rc == -1); } void kiwi_test_fe_read_auth(void) { test_sasl_single_scram_sha_256(); test_sasl_scram_sha_256_first(); test_sasl_scram_sha_256_plus_first(); test_sasl_unsupported_only(); test_sasl_scram_sha_256_plus_only(); test_sasl_empty_list(); test_sasl_scram_sha_256_third(); test_sasl_malformed_no_null_terminator(); } odyssey-1.6.0-rc1/sources/tests/kiwi/test_kiwi_pgoptions.c000066400000000000000000000207341526064202200237450ustar00rootroot00000000000000#include #include #include typedef struct { kiwi_var_type_t t; const char *v; } varinfo_t; static inline varinfo_t var(kiwi_var_type_t type, const char *value) { varinfo_t vi; vi.t = type; vi.v = value; return vi; } static inline void do_pgoptions_test(const char *str, int expected_rc, int count, ...) { va_list ap; va_start(ap, count); kiwi_vars_t expected_vars; kiwi_vars_init(&expected_vars); for (int i = 0; i < count; ++i) { varinfo_t vi = va_arg(ap, varinfo_t); test(kiwi_vars_set(&expected_vars, vi.t, vi.v, strlen(vi.v) + 1) == 0); } kiwi_vars_t vars; kiwi_vars_init(&vars); int rc = kiwi_parse_options_and_update_vars( &vars, str, str != NULL ? strlen(str) : 0); test(rc == expected_rc); if (rc != 0) { /* check error */ } else { for (kiwi_var_type_t type = KIWI_VAR_CLIENT_ENCODING; type < KIWI_VAR_MAX; ++type) { kiwi_var_t *expected_var = kiwi_vars_of(&expected_vars, type); kiwi_var_t *var = kiwi_vars_of(&vars, type); test(var->name_len == expected_var->name_len); test(strncasecmp(var->name, expected_var->name, var->name_len) == 0); test(var->value_len == expected_var->value_len); test(strncmp(var->value, expected_var->value, var->value_len) == 0); } } va_end(ap); } void kiwi_test_pgoptions(void) { do_pgoptions_test("-c statement_timeout=19", 0 /* expected_rc */, 1, /* vars count */ var(KIWI_VAR_STATEMENT_TIMEOUT, "19")); do_pgoptions_test(" -c statement_timeout=19", 0 /* expected_rc */, 1, /* vars count */ var(KIWI_VAR_STATEMENT_TIMEOUT, "19")); do_pgoptions_test("-c statement_timeout=19", 0 /* expected_rc */, 1, /* vars count */ var(KIWI_VAR_STATEMENT_TIMEOUT, "19")); do_pgoptions_test("-c statement_timeout=", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_STATEMENT_TIMEOUT, "")); do_pgoptions_test("--search_path=public", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "public")); do_pgoptions_test("-c search_path=public", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "public")); do_pgoptions_test("-c search_path=\"$user\",\\ public", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "\"$user\", public")); do_pgoptions_test( "-c search_path=public --statement_timeout=1000 -c work_mem=4MB", 0 /* expected_rc */, 3 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "public"), var(KIWI_VAR_STATEMENT_TIMEOUT, "1000"), var(KIWI_VAR_WORK_MEM, "4MB")); do_pgoptions_test( " -c search_path=public --statement_timeout=1000 -c work_mem=4MB", 0 /* expected_rc */, 3 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "public"), var(KIWI_VAR_STATEMENT_TIMEOUT, "1000"), var(KIWI_VAR_WORK_MEM, "4MB")); do_pgoptions_test("-c search_path=my\\ schema", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "my schema")); do_pgoptions_test("-c search_path=path\\\\end", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "path\\end")); do_pgoptions_test("-c search_path=popatf\\", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "popatf")); do_pgoptions_test("-c search_path=popa\\tf", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "popatf")); do_pgoptions_test("-c search_path=popa\\tf", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "popatf")); do_pgoptions_test("-c search_path=popa\\tf", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "popatf")); do_pgoptions_test("--statement-timeout=1000", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_STATEMENT_TIMEOUT, "1000")); do_pgoptions_test("-c search_path=", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "")); do_pgoptions_test("-c search_path=val\\=ue", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "val=ue")); do_pgoptions_test(" -c search_path=public --statement_timeout=1000", 0 /* expected_rc */, 2 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "public"), var(KIWI_VAR_STATEMENT_TIMEOUT, "1000")); do_pgoptions_test("-c search_path=my\\ schema\\\\with\\\\backslash", 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, "my schema\\with\\backslash")); do_pgoptions_test("", 0 /* expected_rc */, 0 /* vars count */); do_pgoptions_test(NULL, -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c statement_timeout", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c statement_timeout= 19", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c statement_timeout =19", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c statement_timeout = 19", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test(" -c statement_timeout = 19 ", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c statement_time=1337", 0 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c intervalstyle=postgres_verbose", 0 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("--unknown_var=some\\ value", 0 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c search_path", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-c -c search_path", -1 /* expected_rc */, 0 /* vars count */); do_pgoptions_test("-x search_path=public", -1 /* expected_rc */, 0 /* vars count */); { char opts[512]; char value[256]; memset(value, 'a', 200); value[200] = 0; snprintf(opts, sizeof(opts), "--search_path=%s", value); do_pgoptions_test(opts, -1 /* expected_rc */, 0 /* vars count */); memset(value, 'a', 200); value[200] = 0; snprintf(opts, sizeof(opts), "--%s=x", value); do_pgoptions_test(opts, -1 /* expected_rc */, 0 /* vars count */); /* max allowed value is KIWI_MAX_VAR_SIZE - 1 chars */ memset(value, 'a', KIWI_MAX_VAR_SIZE - 1); value[KIWI_MAX_VAR_SIZE - 1] = 0; snprintf(opts, sizeof(opts), "--search_path=%s", value); do_pgoptions_test(opts, 0 /* expected_rc */, 1 /* vars count */, var(KIWI_VAR_SEARCH_PATH, value)); } } static void build_startup_packet(char *buf, size_t cap, size_t *out_len, const char *options_value) { size_t pos = 0; /* length placeholder */ buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; /* protocol version 3.0 */ buf[pos++] = 0; buf[pos++] = 3; buf[pos++] = 0; buf[pos++] = 0; memcpy(buf + pos, "user", 5); pos += 5; memcpy(buf + pos, "test", 5); pos += 5; memcpy(buf + pos, "options", 8); pos += 8; size_t value_len = strlen(options_value) + 1; test(cap - pos > value_len + 1); memcpy(buf + pos, options_value, value_len); pos += value_len; buf[pos++] = '\0'; uint32_t len = pos; buf[0] = (len >> 24) & 0xFF; buf[1] = (len >> 16) & 0xFF; buf[2] = (len >> 8) & 0xFF; buf[3] = len & 0xFF; *out_len = pos; } void kiwi_test_be_read_startup_options(void) { char buf[1024]; size_t len; kiwi_be_startup_t su; kiwi_vars_t vars; /* valid options are applied */ build_startup_packet(buf, sizeof(buf), &len, "--search_path=public"); kiwi_be_startup_init(&su); kiwi_vars_init(&vars); test(kiwi_be_read_startup(buf, len, &su, &vars) == KIWI_STARTUP_READ_OK); kiwi_var_t *var = kiwi_vars_get(&vars, KIWI_VAR_SEARCH_PATH); test(var != NULL); test(var->value_len == (int)(strlen("public") + 1)); test(strcmp(var->value, "public") == 0); /* too long option value must reject the whole startup packet */ { char value[256]; memset(value, 'a', 200); value[200] = 0; char opts[512]; snprintf(opts, sizeof(opts), "--search_path=%s", value); build_startup_packet(buf, sizeof(buf), &len, opts); kiwi_be_startup_init(&su); kiwi_vars_init(&vars); test(kiwi_be_read_startup(buf, len, &su, &vars) == KIWI_STARTUP_READ_OPTIONS_ERROR); } /* unknown GUC in options must not reject the whole startup packet */ build_startup_packet(buf, sizeof(buf), &len, "-c intervalstyle=postgres_verbose"); kiwi_be_startup_init(&su); kiwi_vars_init(&vars); test(kiwi_be_read_startup(buf, len, &su, &vars) == KIWI_STARTUP_READ_OK); /* unexpected token must reject the whole startup packet */ build_startup_packet(buf, sizeof(buf), &len, "-x search_path=public"); kiwi_be_startup_init(&su); kiwi_vars_init(&vars); test(kiwi_be_read_startup(buf, len, &su, &vars) == KIWI_STARTUP_READ_OPTIONS_ERROR); } odyssey-1.6.0-rc1/sources/tests/machinarium/000077500000000000000000000000001526064202200210215ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/tests/machinarium/ca.crt000066400000000000000000000037101526064202200221170ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFkzCCA3ugAwIBAgIUNxOZ5JbPVTpzHp85yhdu7cElP00wDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCVNvbWV3aGVyZTEhMB8GA1UECgwY SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDDApvZHlzc2V5LWNhMB4X DTIzMDIxNjA5NDcwMFoXDTMzMDIxMzA5NDcwMFowWTELMAkGA1UEBhMCQVUxEjAQ BgNVBAgMCVNvbWV3aGVyZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg THRkMRMwEQYDVQQDDApvZHlzc2V5LWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEA9+Hwt6uT4R00LqrmgBoezwHdeD9HEPiY5wbNmzVtXsXgryu/aaoY gZPUySwRHIXZYj3leR7En+STmRxOlCbav07JDKJqloNi6gg/AvkwR9PFQVB7biZ2 br0qCxt1XxeAaGlEDES02JKa3QEFyn0+1BJ8m5x8dGZeUHx6cwHm88VZFzrvgRT6 Rw2Hh3R0cY0Avpv63iPrQ96LH8Zg3VZiDJwscw19z3/d/AHO1SUFmUzpR25e3DzC VPr7M/Vlbk+6dcXTt2YnX5qf5eHe6oWkJnvw1ty4rtogSdQPm1Uo2jGu1l0p+2ec aomfB28TiaZrmlM0WUHGKTqRSlURyXEMA0ZoqVYlr5tkkyfvwjF38EKdwPPhWF+R OxUK0rllXcJe4Fkvr3SSaBNwWFHAWkiFF0n3Eyl85F74AahYRou0995W59q25YFU LzVaLyALRRjquy6KYVyq5EzVgcw3Tf5PzTAKTpqF0BldzUvEEC1rhZzxwcgZ7J9S uHkVDhu1nudjjbnlV5i/YDT52Ps4fK2M+avaFaC26M5d4lUu1BNO2NTQwlimTxMA rp6A7NANuluop/C6ZaaAdiCVUFt3Wrsv0PXG1NzeU89Up3J4Xf0LgjdngekDExx8 /T9/eb4Awz3AUXPwmT+7nG+tss3f04V8WdSkIwrfacdGDr3L2sNAka8CAwEAAaNT MFEwHQYDVR0OBBYEFIvwQBisfaeWvutQfrZNdoOVuQXMMB8GA1UdIwQYMBaAFIvw QBisfaeWvutQfrZNdoOVuQXMMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL BQADggIBALo4CO+XPRX1OaunY+XXm9f6wDbw2DnzWKE3aN3/wqR4hdDe3tqcK1O3 9LwRugYJWvraoc/OaAtwaw4CM2h7H5KglVFy9O0ed6FL4i09wK+aTVQhkNGIPfSd fSvjbc3uW2DBEfndGreQEiGyuQsP0f4UwJCuIK/1jrIP0dwpXF3Dv5ua3/rok9Tp Jz+bxIDPYYTgYd2C8vYZBP6NsOG1ft5bh18S9UWOGzdqUnKwEomQF5HeeH9K3cEx NYIXP7hHDPfPzcpHURQ2XxIlRByDPr7/ahST2ID/U55T++ZyUjb0WdV85IUqCvSD irXCvkp0CgbbQsRz6L/Nhd3VyLywt1AEO3PrXysdeANl0UNlV7dChSJcJQvqPGTq 7Cxl9HnQtB5DpdI4ELhWVEHf0rLBYmZ/HdCnhXG+KC04Is//idClhQaII+GukRJT dSmrQdPt6uNMCku/NHbSHXXgn8hnaN40iWNlPXEJmALDEfjq4REu/QOjOjROqByj PdvOeHQOmlhq9WbW2APLckqjdJB6yVm76zLLD8tQa3ZQv3nFEiXnHQMojBvtL5sV bTRZo7MD01bZS1orTqWWv8vAawf2UN70aCKtp56YXMYzH9DI1Oy4tgECirVjiIyE lpzquoYt/OYZLoQg9tUFt0xK3wYG5axdImZBcDV3A6DT53z/DRqr -----END CERTIFICATE----- odyssey-1.6.0-rc1/sources/tests/machinarium/client.crt000066400000000000000000000035121526064202200230120ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFNTCCAx0CFBGRFFVayfHF7bxLj0RlNnuj0RddMA0GCSqGSIb3DQEBCwUAMFkx CzAJBgNVBAYTAkFVMRIwEAYDVQQIDAlTb21ld2hlcmUxITAfBgNVBAoMGEludGVy bmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKb2R5c3NleS1jYTAeFw0yMzAy MTYwOTUyNDFaFw0zMzAyMTMwOTUyNDFaMFUxCzAJBgNVBAYTAkFVMRIwEAYDVQQI DAlTb21ld2hlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEP MA0GA1UEAwwGY2xpZW50MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA tnTh1BWNbi0PT35sALp5AVaqIcHp0FNSc9bqYJRnMdNRkl+CmuQ7PaO8Kr+cMGxY Jbu/Y4zy0NEFnrl17wtZLvvkHybzICrL3cER/22FKXE8roCIh0rDqX47etFloRmK sE/ZNW3Oh11LXTW7o0A6vjV5xsdPQouFzI72nhGQTBMycGxHOK7+HKMS9TOfqfqK wcSOHDvNmj0M8aRKVujSYo3xpsFAut5ieSYPuLVdlfxjPnIdg8oH37dCfbQu0vZj lh5iQxIxLK+74/XCk7WF7ulD9LWXdYtUe1S47OdYlv6l4unFHkm/zsXC3cCjDyIw WfvJZJmYuAiBJuEA5PKINek+JVqB3YvP08FOzAqzuEVI/11XarnQzOhVvZZm9BU4 co09U7svrkFdSyqLuqYIzyCJ9LLUP9AeeULjnLULXAbQJBjUOOY6HLNwwFT6PeiB JveckRKaXqp7EU2tZMwXMnrR6kRKUk+KnMhvnW+b738qUZlWCSJxIS5eGeKGWVNd 4TQ4HySXUJQ8yPKolWLRDQ3acKCdy5gkzJxhvLRglYTYkdg3Zrum55rBOtSPGPxM mb8L1PpCEs04Aq4QoVgD906uZ7hSF57Os25c1SUDpnx3/yR/ia6Uk2uYe3jSX3Rk AalFY8yYWogQfvD1J2SPfZjT6ZlPuSyxjVEf/0DTJwMCAwEAATANBgkqhkiG9w0B AQsFAAOCAgEAmJMlP7zaeV4DORGRiNhIBiOxqvN7yc8kroepVew4XZM4Fqncs1uY VhBS2ofRQfc4bzaiL7fUG8XAYFS9N03LPfNCSJmGqzGsjVWx/btNTk8dHI/7AQUL lAblPkTM4nKysm6zkcrHanzyu87+Z/opJH4Oxaz5ruOZxdHosFyltT/xzEh1v9ir Fqt7m7tCODkkR5URhlbgCJnidj95q6dDXX3PcjVL3ABCDTKQU0GdhCBZ+XXhrbCW 23qF4iB3ggxl+XcE0vbQI0Sg5y7e2/7mMJm2I2OnHT9p4VX0vKjzpqpo4KOwoDtu xQNLFJtltZhQJh539txpj7Bcc0WKhw051oNTQg8zgn+bP4HsYJgvGsxbMt2IewyE lwjMq+R/iGHpvEV9is5jgzhb53o+kHOXdNIDWZdEPDB36jFKi0lWqo/hjEr8Awb6 dHOFIDw5fmfU1JvxITmwcfRQitCmJo+vLXvP2Ehd6ppoL4OJznl+wfL+XOa0bFxn Dn//QDKT6oJ5eh9+Z8XlaQ/xWb5Y72eOR4pUm1qSSj7Aj0jupCDufQzdwnBY0i6v bStLje3nN0ADiF/LnVOhSO3r0y2C0JlSKqLBWJFd1FXFfDS0phuxfm2EJfdN9bRA vkndh7xLje0/OvCxOXqYlWUP4Rjd/4xPyGxN/d0GodqzUNCfXN5k7EE= -----END CERTIFICATE----- odyssey-1.6.0-rc1/sources/tests/machinarium/client.key000066400000000000000000000063101526064202200230110ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC2dOHUFY1uLQ9P fmwAunkBVqohwenQU1Jz1upglGcx01GSX4Ka5Ds9o7wqv5wwbFglu79jjPLQ0QWe uXXvC1ku++QfJvMgKsvdwRH/bYUpcTyugIiHSsOpfjt60WWhGYqwT9k1bc6HXUtd NbujQDq+NXnGx09Ci4XMjvaeEZBMEzJwbEc4rv4coxL1M5+p+orBxI4cO82aPQzx pEpW6NJijfGmwUC63mJ5Jg+4tV2V/GM+ch2Dygfft0J9tC7S9mOWHmJDEjEsr7vj 9cKTtYXu6UP0tZd1i1R7VLjs51iW/qXi6cUeSb/OxcLdwKMPIjBZ+8lkmZi4CIEm 4QDk8og16T4lWoHdi8/TwU7MCrO4RUj/XVdqudDM6FW9lmb0FThyjT1Tuy+uQV1L Kou6pgjPIIn0stQ/0B55QuOctQtcBtAkGNQ45jocs3DAVPo96IEm95yREppeqnsR Ta1kzBcyetHqREpST4qcyG+db5vvfypRmVYJInEhLl4Z4oZZU13hNDgfJJdQlDzI 8qiVYtENDdpwoJ3LmCTMnGG8tGCVhNiR2Ddmu6bnmsE61I8Y/EyZvwvU+kISzTgC rhChWAP3Tq5nuFIXns6zblzVJQOmfHf/JH+JrpSTa5h7eNJfdGQBqUVjzJhaiBB+ 8PUnZI99mNPpmU+5LLGNUR//QNMnAwIDAQABAoICACnJEOMCYJaH6Ua6XD+6dPXy hBohD8wvGOYApUKjGhMWI0tTZlTUyiBuvCxKH5oTZgiWO9mvsPhTtmbIcJLjVXRm KbNctzd5taC+k8Gcsm9wSIZqV1osrCzIBRU2mYDQmauRTdxnnnHv0s+w+50Cm7Fu C8qTYLqLu4PChgZjXVme1+FZWuJHaVjjm/bd8MX75AVGX4llxMt7CdjZWFEZFv2N JnebMJjbT1ARgG470wgDejMV7V5+SVlZ3fdt6X9VAC+kG7rikrJ6OJyXbw1m+Bpj /7UuVVaNJzbri0JuJSLHhPQ3t3YdS7kuH1erw2w21z30ZQYOCySyhemChDLqvHjB Izym8DIbNfR3Ewu2uxfN2sNRE13ca/tK0VuumLWC0OMTZI2iPshYFkPd+6WkiaR+ QT6HMAxa9TlzGVhNGD2wo67mlwmXNN0/twhIncIFBszNOCZjebKHHbGfOqwCT6Yq gD+YKZXSbVpTMNx8MaZPZNt6aGXdYOxVlGVkYUTXaCiFQl5BRJ+M+K/K5MT5wY4K vOZWFk4VNi0JGp4cG39pJdsjGe/3x8MaVjztFQ63uT7pGMuLtztj8nqfeFEKl+51 dR68baGdaVZ19bEsFFxx65xDL6yQAZB8Fmx8N5DSUDZGvEG6xQHnw5N+EG0Wp7QF IwwGOopvVBDGX3BabnGxAoIBAQDkPk+MebT46aAQ7BLGtWHJzU2W/tEUo8YCmI3R 0FywbgoMZ3j4vdljbZt+YSs8u2KwPGHwqbcZKHOJNLxDA8bFDZZwetC6LM+O3eoD R+XuR50fcn3C+ckz8Kn5AavJAetRjoOaE0yxZqwCK3sjeKl5dvKFog2XgOxC8xYf HvGt+L5a53X80oMyVrPvwtTA8lRSYzOawsHVdfpbGVLbGsZeYgVdkzSE7Ivau9pC bocF3d4sN8WuWu3QuL5qEBYbo1ff1N1Dqx3cISOwZ+uO6Iz6UogfLOWkLmmncf+F G9FlHHyHt6d5x0YBSLZH/G+wJdhWEDicVPyYhejOHL+ktWgbAoIBAQDMpSH1c651 u099kNkQBp49Wp58mXeE1u5SkKTK+k0sFpnk1/O3b6fBQQd1L0HRh4jJyRmDk4Yw szA1FwQ46lobceJfTC9jFda+iYY+w2gDoDz4gRHm2SA4ZnhfFlahffZUBEYHaIC/ dzbZnzznjb+McZHiMLqLYqkH/f2O/stC3CyZGXl0Z8eStdD/VxPgM8S3zLT2xEyL bsUq/vtcoYejf843HRunJsnDK652SpnC2sHg6vBDGwdA/3y5Ke5ejxnFrJgF8yHB zxReJenP3e0jygoRWUNRrP16AfCFlC+AOfv20XV0HYhcUZhuOwfIhpiKF+EQyhST PatlJKqs3ns5AoIBAEbUl3cupdzBE61stX749vnbPoLnXRcco+xWT1yXXsxZucCT s9TthbL3ZtPXb8WPGVc7xeEbqfiitwgTV4EsgNjSGKebVX8RPPBHKcIO4+oIS1hg /WoufiQiQX1G6Xqdn9tVcL21040SIbsipzogzecl5KlX2wPzPF6idXD+911X3irz vYfSBGjwwzJasg+WpFhU9MTIP8Lws9rlhvL74ieUH7okXp4JknU06szG5BwL+FCc ljo/eaHWUzEzAMle7XxG4MuJJOEVX6/08AMPZACi8B9MnY9P3/XYyMUUFu1b48Fy WwTNtIndeMPqMPbnJ7W9nvPU4/apR8/+1/JTYZUCggEBAMpHM2DVfo1an0DjStrf X8wPZXaNjyjb9efglzMW4+7/TBB6MKmRkQShjYiUNH+yc0/ZGgIHcO10QONJI+BK LNTR5csCmBhdvszJTMt/BLII0htkINjIp2N4TeTXRLwNsMztCHdMIW6fnLTRBDdw 0fQ1Udh9B/a8h42hzd5pFWq6DM8r8fRQnGD0nGe3n6rOINT4X6tG7s4nqHZOYRD3 jQXcT24EtI+s9GOZKrI8HO4QovD56JC8B5iEsLiprLcExZmxdvbyktxqHpzM5uaT W1Iz422wiK0Ty9ysTB0NE0KwjdAyy+EBTAbMlFnouVHJUL+2yL16sYm0vFSWQKns o2kCggEBANr7/5WRUIB+t4jFcDUloKOtSKNhyxkhT39D4sdm7MvGHKVZVbf/1aX+ OO9Xp3Dm7P9cJ0XjRzrucIrmbqygpDDivXuhE/fE09msFYFuJb/i0O6ERra7gvW1 wl0DR7ZaeVgTNQoBvm+njcW12kns7KoOgq4YJ/qrXXH3WO5vj7H85saSYISmSxKI uWiECBaGFZO6+XVxUtHzkNTk1Q5u3wRwAHfGHPT0WxY4SBiPzdde0EjRqPFaX3zc GqJeapxQYbzGcUDdDZGzrf8XUnhutNevIX/Ft1q7bjB7zHar68E5CTByz6EmjyN0 6mCLjvLokTAGsKwDMVhTniWynz7Tiuc= -----END PRIVATE KEY----- odyssey-1.6.0-rc1/sources/tests/machinarium/generate-test-certs.sh000077500000000000000000000012551526064202200252500ustar00rootroot00000000000000#!/bin/env bash set -eux openssl genrsa -out ca.key 2048 openssl req -new -key ca.key -out ca.csr -nodes -subj "/CN=odyssey-test-cn" openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -nodes -subj "/CN=localhost" openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 openssl genrsa -out client.key 2048 openssl req -new -key client.key -out client.csr -nodes -subj "/CN=localhost" openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 rm ca.key ca.csr ca.srl rm server.csr rm client.csr odyssey-1.6.0-rc1/sources/tests/machinarium/server.crt000066400000000000000000000035121526064202200230420ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFNTCCAx0CFBGRFFVayfHF7bxLj0RlNnuj0RdeMA0GCSqGSIb3DQEBCwUAMFkx CzAJBgNVBAYTAkFVMRIwEAYDVQQIDAlTb21ld2hlcmUxITAfBgNVBAoMGEludGVy bmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKb2R5c3NleS1jYTAeFw0yMzAy MTYwOTUzMjdaFw0zMzAyMTMwOTUzMjdaMFUxCzAJBgNVBAYTAkFVMRIwEAYDVQQI DAlTb21ld2hlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEP MA0GA1UEAwwGc2VydmVyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA zge3jt0Np07fTIUBwYJlNQvvnhYp1E9nnzY5bBRPgHt/5SYwYbapXGamGOK2kuRp Y+VUYxoqQlhRFhNcjgYxXKvv1mRS5H+Y4Ylm6qohXkMXciSAHDVyRjJRQlmA2WfD xM9GT+cpVJDC8HUNzvuJjPpXBUY3SHo3gJBCn7+w9HwjMGYd59qiQ4Iwfe36b13S QyQ1iu2BuKJwE+erOJmBeruCyqJV0CLL5aXTYvyQ8YfnAwVouxx0ZFgpRRHS0pu9 c+JsCjVr9WkvfQ3kuAoNATqPJ/w3yEtrhHAtq/vUlecbBWKImefP2StGMyXiyTtF EmOETTWNBgctHWXRvGgydBTawboincT32tQcGGlJhLHv/DwqCKJrujvxmL7WssTd TfTseflsYDOajz3HB/0d0xuajzWXhnlCQAOLqQCvbvhgY03HzOvgUh3c0wsS3a+t cDBp2OwOWUDz7iohLjir4iAcsrkxqOW1LoTw3kCyp0qQnnlMvk+7Z2g4aqkbAUHy Yc5D9Uxf5IcroFJLwYz9RK6sEcwYoISSO/JXXm/p0OtBpKtnSMstmd55ZlaDnjnG cx72tokDgTTcZ7w9hJUe62QP7IeV7A1Kl0/1P9phD4YygFsu6TP+WNmGf2iDl700 XfAYRmPyIniylat5sJvEHJAftOxKypBEdwd2So4hct8CAwEAATANBgkqhkiG9w0B AQsFAAOCAgEA5GoQ3Q1L/0fUSgB2hJN3wM2oaYY32VwhpMMn1V8n2a6600fj/lmP 4tldeZXzgd7Qb4sbpWiwmSRzUlFyaHZid8fQ7MOKQElJkZdzDONJYE4Zlbtm6uws GfuT2/mCQpChz07xvosHaVEeBnYO1M92aMR8rQ63YwAHPbJVq1H40bnnFbRi8xs9 Ftge19uACes0u3686OdHh2TEa4//ZfcpTG3oY++r6Xm4j0oRUqBFDp54IKUsOJCK b4spy6hoHa78/J9wMWNcc7ppPbe140YPF80yJ9xD1Er3AR6fto9bq8N4ECM2p5VI hQwvfTYpNqlsCv7DsManhEO5VpOL/6sKMIkwfmP4Bsqn+9r/QTw9EkAAeqjrDJTn 2jiucBLlXcxs98lO8g4+dGLIRdXd4YldsNYC3EZdDbHKrKoaIKccu7nU2C7eRdre bgRBztk/ilhwp3Phh3KNzTQRvejkMcuzTRv/47SNGQyR9tJ2kT+QyZTTYw9YIkzB TiH7Na4Nk1osn5bn5HFd1jcgTaPrcndONZiyQ406JAnQBCQ9yID6yQnX0FLsEYlT PFWgRu5AkH0omYHb7qqe/y4HBhUxRPMInvHCthwkPwr6gHMCOHL7/6KdpAfF3i2t UPeYzt47CFCSGUYWrzIbAfYmYxgKnTL0r+/RWBv72pw8zAuYqmHYoUM= -----END CERTIFICATE----- odyssey-1.6.0-rc1/sources/tests/machinarium/server.key000066400000000000000000000063101526064202200230410ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDOB7eO3Q2nTt9M hQHBgmU1C++eFinUT2efNjlsFE+Ae3/lJjBhtqlcZqYY4raS5Glj5VRjGipCWFEW E1yOBjFcq+/WZFLkf5jhiWbqqiFeQxdyJIAcNXJGMlFCWYDZZ8PEz0ZP5ylUkMLw dQ3O+4mM+lcFRjdIejeAkEKfv7D0fCMwZh3n2qJDgjB97fpvXdJDJDWK7YG4onAT 56s4mYF6u4LKolXQIsvlpdNi/JDxh+cDBWi7HHRkWClFEdLSm71z4mwKNWv1aS99 DeS4Cg0BOo8n/DfIS2uEcC2r+9SV5xsFYoiZ58/ZK0YzJeLJO0USY4RNNY0GBy0d ZdG8aDJ0FNrBuiKdxPfa1BwYaUmEse/8PCoIomu6O/GYvtayxN1N9Ox5+WxgM5qP PccH/R3TG5qPNZeGeUJAA4upAK9u+GBjTcfM6+BSHdzTCxLdr61wMGnY7A5ZQPPu KiEuOKviIByyuTGo5bUuhPDeQLKnSpCeeUy+T7tnaDhqqRsBQfJhzkP1TF/khyug UkvBjP1ErqwRzBighJI78ldeb+nQ60Gkq2dIyy2Z3nlmVoOeOcZzHva2iQOBNNxn vD2ElR7rZA/sh5XsDUqXT/U/2mEPhjKAWy7pM/5Y2YZ/aIOXvTRd8BhGY/IieLKV q3mwm8QckB+07ErKkER3B3ZKjiFy3wIDAQABAoICAG2UqyugJQfsqlW/lHyAxyWc sJirfxn+FcYXiBkVAGf8x7xwQGFoTtHngsZPpv2f4PIR4n1HSFy7Ln2IVLzxTEXW 6RxlhbUkrIKgmw2K6W6CTrEV+tGGF76AOroIcsCvMO+3stUCxPfGevh7uZlyLem+ qJQUkYjKAEUv9U+UHCEc+XZfid5sactwLix8a6vaAeBWHOhEhK9YLjZYDLhw5O4L V3hBOVUXfM0rErBeVxUvzE3ODbMfYkSQo3qqDTCZRv2g2wfe/AmuJWTlA9Zi+iHC LKv+uQieHJR3OcN0kNAd+EA39SGuqtz96DuCFgojrQwnxBFwvw4KuRMeuZv064ff uTOn+sreHVgrzy/7ZpDIv4xRh7Imv5Hta0MmliHRa+ZjpEHg2DRK//wTWMbh0Ny1 wPxAfU3NJeDlaYqExSaIhr4XDKCYjCk7at/RCFYqmdTvdAuEKC+Qt+fff5lBKNgE PBCabj5VnTYQ8kP5Jm4kfU4raX4mXptazGHvxIBNM22UQvWWIk0d8Ap0TRnZJNg2 HJPKCxMcwX435CxeHpKoOv5n/Qw3oF+Z52GBOzskeJojrSn2zzxPUiA+X0+dZrNZ yJ4Jck4IYmOWEo+9lV5gu7C9PSlPTokjlholKgunECpKq0t/44EzuMPVEBamPsyb 7InkiS2u6gW8nl97nCchAoIBAQDsz2KBjR8hUGi8DqTv5Fo15zxTEOMk6XsXnrSC K8mVJjD7nMF5YcxZmQ2i66KZOHDVQx1CqH5/rK3qOoPJHgtSku9v7ZoMSon1N3Hz lXZntuR0MNhO4Twb70wXo+C0rCIFwHPChc1Eukvg9jDWRDDGX7eQ4hZSmYcrdeAU yEYatrZyI6KPn1Xp7C9Ojl3P+eF3Vp4CaHCN2mSLAwdMogAmIHc+nDBQySZV5nUs 68Hq2cC2HKqSfZm6r96CxRz2C1N6tEFc6ctg/fnEAUn9yert7u0go1ft5ykExRCY LWsdTV8Ev9j37kMWc5o2TzkeeSPMrhvEaojRPQNQyswLjOhxAoIBAQDeuc2vaUxA MjTmclGxh+r0PjzWHPXhnRAjdK2vR9N4++iXJJG09NKfKzEhjWuGk+FioG7CD4rN hzg5gtogcXQ7w309TQPASRZIebN3TWgzGRMjzoSJuxD9kYWLRS31fzs43E8/40AT 6CcB5UT2ULWGDZ17UdQQGrthdhbVDXOsZtr9aQpHIHbgMJxG/qhAXj+gQ2S2egAB uulZqou4hjqcLi30Mly5QVRID9YJFtzYvBpDXyDBPwBkcGLEIGIub0+NUdQeNOO3 uzLaTuJVB1WrKQtHbYSYFzpGFWbyNQQkX2cg8nVBxy+gN6fq6QB5A+MsWxqilXuj /VWJ2q42HThPAoIBAHUd673eqXK0bdfz9iaebO8TuYXraCstPa9k0ik+tqeJB9io ohcCAyqcw1274+KbQ1J1qtb4tfc205xv7BVfQoJIIRreC73gb3JTRYwOc0y/6yR7 s77vLecy5hMa/VrddsYZdCbUPbyr1jMgDu8pMmYpmIOTDS80tuELnxMoKWO00W6J qTAWwUq7gHn9dvAQyS1YLTVLqb6+L9OAX+/KfwBcNh1E6bBKiycYnwLkZjdcEi7+ 5m0kqQ7nQ8iw663T1ClvVJQ7HFaPxSLm8E0Pu5yI+cWWgzophOPTAm4AqGUJiuCy cP/NWmiUK8SIUdt8VfQ4d4sglSHGZwliAqADxEECggEAeEkcfnl9xsXWsAZZfGmN SK88J96oJFqw1pUzIBDIcvSJXudPEc2O2ot3E0nPInVHKf6/EmtWaT137gdvSko7 nfMr9ilbt0NBl46yj+HsPnxyS8pC5idzO56V4utPsOAv2buRyU0v1BBy0KFDp0tb XbSIsos03iWxwScHmbSUHi3GY0mRVWyBBv75x5XMxEaiUilTgQFOc6DAWb6zispj aOm/w7xXO22+2NREodAJIbwk6vG7POKLkbLohEeA/tb6vbXNuyqROS8OfbxQm99E MxYNk1vgWkthG2x/lptvSz2C8tY+4XFzlTG5rDJ+IN0qmU/yiI/2oFA3HCZQI8WQ HQKCAQEAr05jf3nSbgG1HJ4tnKi+jCu49DI+zKXSHz/b3XG4r7hQ9QueFgBS5xMW xGm1MU4aYXw5lUClaJOttk9sO/kJ2p5YsxU91Qv49kEAP+liybTILuLcRqimcdyb ZjiHcSP+7y1pnDfAJMdySvAFZfwd/2/dEHXJOJGDJnEZxmZA9zA5la84qq44aWWR LgkJQmtZ/Jflp7MbtfDL7iPxc/pYwmgeDL2VbTySnwba1etelf3uRdVzVAqodkgY NQcGnIhazfJoKKC/E1+o9kfJX8+adkm7jn3oTjo9/Vr49stBjMZ+Ijo4tBSvGtqM xnqWYI+SoSSLoRAcs8YpZVzDbyoPgg== -----END PRIVATE KEY----- odyssey-1.6.0-rc1/sources/tests/machinarium/test_accept_cancel.c000066400000000000000000000022221526064202200247660ustar00rootroot00000000000000 #include #include #include #include #include #include static void test_server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, 100); test(rc == -1); test(machine_cancelled()); test(!machine_timedout()); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void test_waiter(void *arg) { (void)arg; int id = machine_coroutine_create(test_server, NULL); test(id != -1); machine_sleep(0); int rc; rc = machine_cancel(id); test(rc == 0); rc = machine_join(id); test(rc == 0); } void machinarium_test_accept_cancel(void) { machinarium_init(); int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_accept_timeout.c000066400000000000000000000016111526064202200252300ustar00rootroot00000000000000 #include #include #include #include #include #include static void test_server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, 100); test(rc == -1); test(machine_timedout()); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } void machinarium_test_accept_timeout(void) { machinarium_init(); int id; id = machine_create("test", test_server, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_advice_keepalive_usr_timeout.c000066400000000000000000000004371526064202200301470ustar00rootroot00000000000000#include #include #include void machinarium_test_advice_keepalive_usr_timeout(void) { test(mm_io_advice_keepalive_usr_timeout(100, 10, 3) == 129500); test(mm_io_advice_keepalive_usr_timeout(15, 10, 5) == 64500); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_cancel.c000066400000000000000000000014401526064202200251400ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine2(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg == NULL); test(machine_cancelled()); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int id; id = machine_coroutine_create(test_coroutine2, NULL); machine_sleep(0); int rc = machine_cancel(id); test(rc == 0); machine_join(id); machine_channel_free(channel); } void machinarium_test_channel_cancel(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_create.c000066400000000000000000000007241526064202200251620ustar00rootroot00000000000000 #include #include static void test_coroutine(void *arg) { (void)arg; machine_channel_t *channel; channel = machine_channel_create(); test(channel != NULL); machine_channel_free(channel); } void machinarium_test_channel_create(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_rw0.c000066400000000000000000000013541526064202200244270ustar00rootroot00000000000000 #include #include static void test_coroutine(void *arg) { (void)arg; machine_channel_t *channel; channel = machine_channel_create(); test(channel != NULL); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 123); machine_channel_write(channel, msg); machine_msg_t *msg_in; msg_in = machine_channel_read(channel, 0); test(msg_in != NULL); test(msg_in == msg); machine_msg_free(msg); machine_channel_free(channel); } void machinarium_test_channel_rw0(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_rw1.c000066400000000000000000000022031526064202200244220ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine2(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 123); machine_msg_free(msg); msg = machine_msg_create(0); machine_msg_set_type(msg, 321); machine_channel_write(channel, msg); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int id; id = machine_coroutine_create(test_coroutine2, NULL); machine_msg_t *msg; msg = machine_msg_create(0); machine_msg_set_type(msg, 123); test(msg != NULL); machine_channel_write(channel, msg); mm_yield; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 321); machine_msg_free(msg); machine_join(id); machine_channel_free(channel); } void machinarium_test_channel_rw1(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_rw2.c000066400000000000000000000022361526064202200244310ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine2(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 123); machine_msg_free(msg); msg = machine_msg_create(0); machine_msg_set_type(msg, 321); machine_channel_write(channel, msg); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int id; id = machine_coroutine_create(test_coroutine2, NULL); machine_sleep(0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 123); machine_channel_write(channel, msg); machine_sleep(0); msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 321); machine_msg_free(msg); machine_join(id); machine_channel_free(channel); } void machinarium_test_channel_rw2(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_rw3.c000066400000000000000000000035351526064202200244350ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine_a(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 1); machine_msg_free(msg); } static void test_coroutine_b(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 2); machine_msg_free(msg); } static void test_coroutine_c(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 3); machine_msg_free(msg); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int a; a = machine_coroutine_create(test_coroutine_a, NULL); test(a != -1); machine_sleep(0); int b; b = machine_coroutine_create(test_coroutine_b, NULL); test(b != -1); machine_sleep(0); int c; c = machine_coroutine_create(test_coroutine_c, NULL); test(c != -1); machine_sleep(0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 1); machine_channel_write(channel, msg); msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 2); machine_channel_write(channel, msg); msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 3); machine_channel_write(channel, msg); machine_sleep(0); machine_sleep(0); machine_sleep(0); machine_join(a); machine_join(b); machine_join(c); machine_channel_free(channel); } void machinarium_test_channel_rw3(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_rw4.c000066400000000000000000000035341526064202200244350ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine_a(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 1); machine_msg_free(msg); } static void test_coroutine_b(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 2); machine_msg_free(msg); } static void test_coroutine_c(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 3); machine_msg_free(msg); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int a; a = machine_coroutine_create(test_coroutine_a, NULL); test(a != -1); machine_sleep(0); int b; b = machine_coroutine_create(test_coroutine_b, NULL); test(b != -1); machine_sleep(0); int c; c = machine_coroutine_create(test_coroutine_c, NULL); test(c != -1); machine_sleep(0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 1); machine_channel_write(channel, msg); machine_sleep(0); msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 2); machine_channel_write(channel, msg); machine_sleep(0); msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 3); machine_channel_write(channel, msg); machine_sleep(0); machine_join(a); machine_join(b); machine_join(c); machine_channel_free(channel); } void machinarium_test_channel_rw4(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_shared_create.c000066400000000000000000000007331526064202200265100ustar00rootroot00000000000000 #include #include static void test_coroutine(void *arg) { (void)arg; machine_channel_t *channel; channel = machine_channel_create(); test(channel != NULL); machine_channel_free(channel); } void machinarium_test_channel_shared_create(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_shared_rw0.c000066400000000000000000000013661526064202200257600ustar00rootroot00000000000000 #include #include static void test_coroutine(void *arg) { (void)arg; machine_channel_t *channel; channel = machine_channel_create(); test(channel != NULL); machine_msg_t *msg; msg = machine_msg_create(0); machine_msg_set_type(msg, 123); test(msg != NULL); machine_channel_write(channel, msg); machine_msg_t *msg_in; msg_in = machine_channel_read(channel, 0); test(msg_in != NULL); test(msg_in == msg); machine_msg_free(msg_in); machine_channel_free(channel); } void machinarium_test_channel_shared_rw0(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_shared_rw1.c000066400000000000000000000022221526064202200257510ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine2(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 123); machine_msg_free(msg); msg = machine_msg_create(0); machine_msg_set_type(msg, 321); machine_channel_write(channel, msg); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int id; id = machine_coroutine_create(test_coroutine2, NULL); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 123); machine_channel_write(channel, msg); machine_sleep(0); msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 321); machine_msg_free(msg); machine_join(id); machine_channel_free(channel); } void machinarium_test_channel_shared_rw1(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_shared_rw2.c000066400000000000000000000022701526064202200257550ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static void test_coroutine2(void *arg) { (void)arg; machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 123); machine_msg_free(msg); msg = machine_msg_create(0); machine_msg_set_type(msg, 321); machine_channel_write(channel, msg); } static void test_coroutine(void *arg) { (void)arg; channel = machine_channel_create(); test(channel != NULL); int id; id = machine_coroutine_create(test_coroutine2, NULL); machine_sleep(0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, 123); machine_channel_write(channel, msg); machine_sleep(0); machine_sleep(0); msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); test(machine_msg_type(msg) == 321); machine_msg_free(msg); machine_join(id); machine_channel_free(channel); } void machinarium_test_channel_shared_rw2(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_channel_timeout.c000066400000000000000000000010531526064202200254010ustar00rootroot00000000000000 #include #include static void test_coroutine(void *arg) { (void)arg; machine_channel_t *channel; channel = machine_channel_create(); machine_msg_t *msg; msg = machine_channel_read(channel, 100); test(msg == NULL); test(channel != NULL); machine_channel_free(channel); } void machinarium_test_channel_timeout(void) { machinarium_init(); int id; id = machine_create("test", test_coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_client_server0.c000066400000000000000000000037301526064202200251530ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); char text[] = "hello world"; rc = machine_msg_write(msg, text, sizeof(text)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 12, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "hello world", 12) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); /* eof */ test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_client_server0(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_client_server1.c000066400000000000000000000054271526064202200251610ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 0, UINT32_MAX); test(rc == 0); rc = mm_io_attach(client); test(rc == 0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); char text[] = "hello world" "HELLO WORLD" "a" "b" "c" "333"; rc = machine_msg_write(msg, text, sizeof(text)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 11, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "hello world", 11) == 0); machine_msg_free(msg); msg = machine_read(client, 11, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "HELLO WORLD", 11) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "a", 1) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "b", 1) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "c", 1) == 0); machine_msg_free(msg); msg = machine_read(client, 4, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "333", 4) == 0); machine_msg_free(msg); /* eof */ msg = machine_read(client, 1, UINT32_MAX); test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_client_server1(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_client_server2.c000066400000000000000000000046331526064202200251600ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); int i = 0; for (;;) { machine_msg_t *msg; msg = machine_read(client, sizeof(i), UINT32_MAX); test(msg != NULL); i = *(int *)machine_msg_data(msg); machine_msg_free(msg); i++; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, (void *)&i, sizeof(i)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); if (i == 1000) { break; } } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); int i = 0; for (;;) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, (void *)&i, sizeof(i)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); msg = machine_read(client, sizeof(i), UINT32_MAX); test(msg != NULL); i = *(int *)machine_msg_data(msg); machine_msg_free(msg); if (i == 1000) { break; } } /* eof */ machine_msg_t *msg; msg = machine_read(client, sizeof(i), UINT32_MAX); test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_client_server2(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_client_server_unix_socket.c000066400000000000000000000040451526064202200275060ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 12, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "hello world", 12) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); /* eof */ test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); char text[] = "hello world"; rc = machine_msg_write(msg, text, sizeof(text)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); unlink("_un_test"); } static void test_cs(void *arg) { unlink("_un_test"); (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_client_server_unix_socket(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_client_server_unix_socket_no_msg.c000066400000000000000000000032471526064202200310530ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg = machine_read(client, 1, UINT32_MAX); /* eof */ test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); unlink("_un_test"); } static void test_cs(void *arg) { unlink("_un_test"); (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_client_server_unix_socket_no_msg(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_condition0.c000066400000000000000000000116571526064202200243040ustar00rootroot00000000000000 #include #include #include #include static machine_cond_t *condition = NULL; static void test_condition_coroutine(void *arg) { (void)arg; machine_cond_signal(condition); machine_stop_current(); } static void test_waiter(void *arg) { (void)arg; condition = machine_cond_create(); test(condition != NULL); int64_t a; a = machine_coroutine_create(test_condition_coroutine, NULL); test(a != -1); int rc; rc = machine_cond_wait(condition, UINT32_MAX); test(rc == 0); machine_stop_current(); } static void test_simple(void) { int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); } atomic_uint_fast64_t success_counter; atomic_uint_fast64_t timeout_counter; static void awaiter_success(void *arg) { mm_cond_t *cond = arg; int rc = mm_cond_wait(cond, UINT32_MAX); test(rc == 0); atomic_fetch_add(&success_counter, 1); } typedef struct { mm_cond_t *cond; mm_wait_flag_t *end_flag; } timeout_arg_t; static void awaiter_timeout(void *arg) { timeout_arg_t *a = arg; mm_cond_t *cond = a->cond; int rc = mm_cond_wait(cond, 100); test(rc == -1 && machine_errno() == ETIMEDOUT); atomic_fetch_add(&timeout_counter, 1); mm_wait_flag_set(a->end_flag); } static void several_awaiters(void *a) { (void)a; mm_cond_t cond; mm_cond_init(&cond); atomic_init(&success_counter, 0); int64_t a1; a1 = machine_coroutine_create(awaiter_success, &cond); test(a1 != -1); machine_sleep(0); int64_t a2; a2 = machine_coroutine_create(awaiter_success, &cond); test(a2 != -1); machine_sleep(0); int64_t a3; a3 = machine_coroutine_create(awaiter_success, &cond); test(a3 != -1); machine_sleep(0); mm_cond_signal(&cond); int rc = machine_join(a1); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); rc = machine_join(a2); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); rc = machine_join(a3); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); test(atomic_load(&success_counter) == 3); } static void test_several_awaiters(void) { int id; id = machine_create("several_awaiter", several_awaiters, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); } static void several_awaiters_timeout(void *a) { (void)a; mm_cond_t cond; mm_cond_init(&cond); atomic_init(&success_counter, 0); atomic_init(&timeout_counter, 0); timeout_arg_t targ; targ.cond = &cond; targ.end_flag = mm_wait_flag_create(); int64_t a1; a1 = machine_coroutine_create(awaiter_timeout, &targ); test(a1 != -1); machine_sleep(0); int64_t a2; a2 = machine_coroutine_create(awaiter_success, &cond); test(a2 != -1); machine_sleep(0); int64_t a3; a3 = machine_coroutine_create(awaiter_success, &cond); test(a3 != -1); machine_sleep(0); mm_wait_flag_wait(targ.end_flag, UINT32_MAX); mm_cond_signal(&cond); int rc = machine_join(a1); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); rc = machine_join(a2); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); rc = machine_join(a3); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); test(atomic_load(&success_counter) == 2); test(atomic_load(&timeout_counter) == 1); mm_wait_flag_destroy(targ.end_flag); } static void test_several_awaiters_timeout(void) { int id; id = machine_create("several_awaiter", several_awaiters_timeout, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); } atomic_uint_fast64_t propagated_counter; static void awaiter_propagated(void *arg) { mm_cond_t *cond = arg; int rc = mm_cond_wait(cond, UINT32_MAX); test(rc == MM_COND_WAIT_OK_PROPAGATED); atomic_fetch_add(&propagated_counter, 1); } static void several_awaiters_propagated(void *a) { (void)a; mm_cond_t src, dst; mm_cond_init(&src); mm_cond_init(&dst); mm_cond_propagate(&src, &dst); atomic_init(&propagated_counter, 0); int64_t a1 = machine_coroutine_create(awaiter_propagated, &dst); test(a1 != -1); machine_sleep(0); int64_t a2 = machine_coroutine_create(awaiter_propagated, &dst); test(a2 != -1); machine_sleep(0); int64_t a3 = machine_coroutine_create(awaiter_propagated, &dst); test(a3 != -1); machine_sleep(0); mm_cond_signal(&src); int rc = machine_join(a1); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); rc = machine_join(a2); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); rc = machine_join(a3); test(rc == 0 || (rc == -1 && machine_errno() == ENOENT)); test(atomic_load(&propagated_counter) == 3); } static void test_several_awaiters_propagated(void) { int id; id = machine_create("several_awaiter_propagated", several_awaiters_propagated, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); } void machinarium_test_condition0(void) { machinarium_init(); test_simple(); test_several_awaiters(); test_several_awaiters_timeout(); test_several_awaiters_propagated(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_config.c000066400000000000000000000003541526064202200234730ustar00rootroot00000000000000 #include #include #include void machinarium_test_config(void) { machinarium_set_pool_size(1); machinarium_init(); test(machinarium_init() == -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_connect.c000066400000000000000000000021051526064202200236530ustar00rootroot00000000000000 #include #include #include #include #include #include static void test_connect_coroutine(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(81); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); if (rc == -1) { int errno_ = machine_errno(); test(errno_ == ECONNREFUSED || errno_ == ECONNRESET); } else { mm_io_close(client); } mm_io_free(client); } static void test_waiter(void *arg) { (void)arg; int id = machine_coroutine_create(test_connect_coroutine, NULL); test(id != -1); machine_sleep(0); int rc; rc = machine_join(id); test(rc == 0); machine_stop_current(); } void machinarium_test_connect(void) { machinarium_init(); int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_connect_cancel0.c000066400000000000000000000017761526064202200252550ustar00rootroot00000000000000 #include #include #include #include #include #include static void test_connect_coroutine(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("8.8.8.8"); sa.sin_port = htons(1234); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == -1); test(machine_cancelled()); mm_io_free(client); } static void test_waiter(void *arg) { (void)arg; int id = machine_coroutine_create(test_connect_coroutine, NULL); test(id != -1); machine_sleep(0); int rc; rc = machine_cancel(id); test(rc == 0); rc = machine_join(id); test(rc == 0); } void machinarium_test_connect_cancel0(void) { machinarium_init(); int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_connect_cancel1.c000066400000000000000000000020311526064202200252370ustar00rootroot00000000000000 #include #include #include #include #include #include static void test_connect_coroutine(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("8.8.8.8"); sa.sin_port = htons(1234); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == -1); test(machine_cancelled()); mm_io_free(client); } static void test_waiter(void *arg) { (void)arg; int id = machine_coroutine_create(test_connect_coroutine, NULL); test(id != -1); int rc; rc = machine_cancel(id); test(rc == 0); machine_sleep(0); rc = machine_join(id); test(rc == -1); machine_stop_current(); } void machinarium_test_connect_cancel1(void) { machinarium_init(); int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_connect_timeout.c000066400000000000000000000016621526064202200254300ustar00rootroot00000000000000 #include #include #include #include #include #include static void test_connect_coroutine(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("213.180.204.3"); sa.sin_port = htons(80); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, 0); test(rc == -1); mm_io_free(client); } static void test_waiter(void *arg) { (void)arg; int id = machine_coroutine_create(test_connect_coroutine, NULL); test(id != -1); machine_sleep(0); int rc; rc = machine_join(id); test(rc == 0); } void machinarium_test_connect_timeout(void) { machinarium_init(); int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_context_switch.c000066400000000000000000000011701526064202200252700ustar00rootroot00000000000000 #include #include static int csw = 0; static void csw_worker(void *arg) { (void)arg; while (csw < 100000) { machine_sleep(0); csw++; } } static void csw_runner(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(csw_worker, NULL); test(rc != -1); rc = machine_join(rc); test(rc != -1); test(csw == 100000); machine_stop_current(); } void machinarium_test_context_switch(void) { machinarium_init(); int id; id = machine_create("test", csw_runner, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_coroutine_names.c000066400000000000000000000045731526064202200254270ustar00rootroot00000000000000#include #include static inline void sleepy(void *arg) { (void)arg; machine_sleep(50); } static inline void test_coro_names(void *arg) { (void)arg; int64_t coro1; coro1 = machine_coroutine_create_named(sleepy, NULL, NULL); test(coro1 != -1); int64_t coro2; coro2 = machine_coroutine_create_named(sleepy, NULL, ""); test(coro2 != -1); int64_t coro3; coro3 = machine_coroutine_create_named(sleepy, NULL, "a"); test(coro3 != -1); int64_t coro4; coro4 = machine_coroutine_create_named(sleepy, NULL, "ab"); test(coro4 != -1); int64_t coro5; coro5 = machine_coroutine_create_named(sleepy, NULL, "abcdef"); test(coro5 != -1); int64_t coro6; coro6 = machine_coroutine_create_named(sleepy, NULL, "abcdefghqwerty"); test(coro6 != -1); int64_t coro7; coro7 = machine_coroutine_create_named(sleepy, NULL, "abcdefghqwertyi"); test(coro7 != -1); int64_t coro8; coro8 = machine_coroutine_create_named(sleepy, NULL, "abcdefghqwertyi_"); test(coro8 != -1); int64_t coro9; coro9 = machine_coroutine_create_named(sleepy, NULL, "abcdefghqwertyi_a"); test(coro9 != -1); int64_t coro10; coro10 = machine_coroutine_create_named(sleepy, NULL, "abcdefghqwertyi_aa"); test(coro10 != -1); test(strcmp(machine_coroutine_get_name(coro1), "") == 0); test(strcmp(machine_coroutine_get_name(coro2), "") == 0); test(strcmp(machine_coroutine_get_name(coro3), "a") == 0); test(strcmp(machine_coroutine_get_name(coro4), "ab") == 0); test(strcmp(machine_coroutine_get_name(coro5), "abcdef") == 0); test(strcmp(machine_coroutine_get_name(coro6), "abcdefghqwerty") == 0); test(strcmp(machine_coroutine_get_name(coro7), "abcdefghqwertyi") == 0); test(strcmp(machine_coroutine_get_name(coro8), "abcdefghqwertyi") == 0); test(strcmp(machine_coroutine_get_name(coro9), "abcdefghqwertyi") == 0); test(strcmp(machine_coroutine_get_name(coro10), "abcdefghqwertyi") == 0); machine_join(coro1); machine_join(coro2); machine_join(coro3); machine_join(coro4); machine_join(coro5); machine_join(coro6); machine_join(coro7); machine_join(coro8); machine_join(coro9); machine_join(coro10); } void machinarium_test_coroutine_names(void) { machinarium_init(); int id; id = machine_create("test_coro_names", test_coro_names, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_create0.c000066400000000000000000000006621526064202200235530ustar00rootroot00000000000000 #include #include static int coroutine_call = 0; static void coroutine(void *arg) { (void)arg; coroutine_call++; machine_stop_current(); } void machinarium_test_create0(void) { machinarium_init(); int id; id = machine_create("test", coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); test(coroutine_call == 1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_create1.c000066400000000000000000000014101526064202200235440ustar00rootroot00000000000000 #include #include static int a = 0; static int b = 0; static int c = 0; static void test_a(void *arg) { (void)arg; a++; } static void test_b(void *arg) { (void)arg; b++; } static void test_c(void *arg) { (void)arg; c++; } void machinarium_test_create1(void) { machinarium_init(); int a_id; a_id = machine_create("a", test_a, NULL); test(a_id != -1); int b_id; b_id = machine_create("b", test_b, NULL); test(b_id != -1); int c_id; c_id = machine_create("c", test_c, NULL); test(c_id != -1); int rc; rc = machine_wait(a_id); test(rc != -1); test(a == 1); rc = machine_wait(b_id); test(rc != -1); test(b == 1); rc = machine_wait(c_id); test(rc != -1); test(c == 1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_eventfd.c000066400000000000000000000050011526064202200236530ustar00rootroot00000000000000 #include #include #include #include #include #include static void writer(void *a) { mm_eventfd_t *efd = a; /* TODO: maybe some better way to wait reader to be in awaiting */ machine_sleep(1000); int rc = mm_eventfd_write(efd, 30 * 1000); test(rc == 0); } static void reader(void *a) { mm_eventfd_t *efd = a; test(mm_eventfd_attach(efd) == 0); int rc = mm_eventfd_read(efd, 30 * 1000); test(rc == 0); test(mm_eventfd_detach(efd) == 0); } static void reader_timeout(void *a) { mm_eventfd_t *efd = a; test(mm_eventfd_attach(efd) == 0); int rc = mm_eventfd_read(efd, 1000); test(rc == -1); test(mm_errno_get() == ETIMEDOUT); test(mm_eventfd_detach(efd) == 0); } static void test_readwrite(void *a) { (void)a; mm_eventfd_t efd; test(mm_eventfd_init(&efd) == 0); int rid = machine_create("reader_timeout", reader_timeout, &efd); test(rid != -1); int rc = machine_wait(rid); test(rc != -1); rid = machine_create("reader", reader, &efd); test(rid != -1); int wid = machine_create("writer", writer, &efd); test(wid != -1); rc = machine_wait(wid); test(rc != -1); rc = machine_wait(rid); test(rc != -1); mm_eventfd_destroy(&efd); } static void test_peering(void *a) { (void)a; mm_eventfd_t efd; test(mm_eventfd_init(&efd) == 0); mm_eventfd_attach(&efd); mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(57778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_eventfd_peer_to(&efd, server); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, 1000); test(rc == -1); test(mm_errno_get() == ETIMEDOUT); int wid = machine_create("writer", writer, &efd); test(wid != -1); rc = mm_io_accept(server, &client, 16, 1, 30 * 1000); test(rc == -1); test(mm_errno_get() == EAGAIN); mm_eventfd_remove_peer_to(&efd, server); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); rc = machine_wait(wid); test(rc != -1); mm_eventfd_destroy(&efd); } void machinarium_test_eventfd(void) { machinarium_init(); int id; id = machine_create("readwrite", test_readwrite, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); id = machine_create("peering", test_peering, NULL); test(id != -1); rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_getaddrinfo0.c000066400000000000000000000011201526064202200245640ustar00rootroot00000000000000 #include #include #include static void test_gai(void *arg) { (void)arg; mm_addrinfo_t *res = NULL; int rc = mm_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { mm_freeaddrinfo(res); } } } void machinarium_test_getaddrinfo0(void) { machinarium_init(); int id; id = machine_create("test", test_gai, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_getaddrinfo1.c000066400000000000000000000020511526064202200245710ustar00rootroot00000000000000 #include #include #include static void test_gai0(void *arg) { (void)arg; mm_addrinfo_t *res = NULL; int rc = mm_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { mm_freeaddrinfo(res); } } } static void test_gai1(void *arg) { (void)arg; mm_addrinfo_t *res = NULL; int rc = mm_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { mm_freeaddrinfo(res); } } } static void test_gai(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(test_gai0, NULL); test(rc != -1); rc = machine_coroutine_create(test_gai1, NULL); test(rc != -1); } void machinarium_test_getaddrinfo1(void) { machinarium_init(); int id; id = machine_create("test", test_gai, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_getaddrinfo2.c000066400000000000000000000016661526064202200246050ustar00rootroot00000000000000 #include #include #include static int gai_complete = 0; static void test_gai_coroutine(void *arg) { (void)arg; mm_addrinfo_t *res = NULL; int rc = mm_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { mm_freeaddrinfo(res); } } gai_complete++; } static void test_gai(void *arg) { (void)arg; int rc; int workers[100]; int i; for (i = 0; i < 100; i++) { rc = machine_coroutine_create(test_gai_coroutine, NULL); test(rc != -1); workers[i] = rc; } for (i = 0; i < 100; i++) { machine_join(workers[i]); } test(gai_complete == 100); } void machinarium_test_getaddrinfo2(void) { machinarium_init(); int id; id = machine_create("test", test_gai, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_heap.c000066400000000000000000000063571526064202200231540ustar00rootroot00000000000000#include #include #include typedef struct { mm_heap_node_t hnode; int value; } el_t; static inline int cmp(const mm_heap_node_t *a, const mm_heap_node_t *b) { const el_t *aa = mm_container_of(a, el_t, hnode); const el_t *bb = mm_container_of(b, el_t, hnode); if (aa->value < bb->value) { return 1; } return 0; } static void test_min_pop_empty(void) { mm_heap_t h; mm_heap_init(&h, cmp); test(mm_heap_validate(&h)); test(mm_heap_size(&h) == 0); test(mm_heap_min(&h) == NULL); test(mm_heap_pop(&h) == NULL); } static void test_push_pop(void) { mm_heap_t h; mm_heap_init(&h, cmp); el_t el; el.value = 1337; mm_heap_push(&h, &el.hnode); test(mm_heap_size(&h) == 1); test(mm_heap_min(&h) == &el.hnode); test(mm_heap_validate(&h)); mm_heap_node_t *e = mm_heap_pop(&h); test(e == &el.hnode); test(mm_container_of(e, el_t, hnode)->value == 1337); test(mm_heap_size(&h) == 0); test(mm_heap_min(&h) == NULL); test(mm_heap_pop(&h) == NULL); } static void test_push_min(void) { mm_heap_t h; mm_heap_init(&h, cmp); el_t el1; el1.value = 1337; mm_heap_push(&h, &el1.hnode); el_t el2; el2.value = -1337; mm_heap_push(&h, &el2.hnode); test(mm_heap_validate(&h)); test(mm_heap_size(&h) == 2); test(mm_heap_min(&h) == &el2.hnode); mm_heap_node_t *e = mm_heap_pop(&h); test(e == &el2.hnode); test(mm_container_of(e, el_t, hnode)->value == -1337); test(mm_heap_size(&h) == 1); e = mm_heap_pop(&h); test(e == &el1.hnode); test(mm_container_of(e, el_t, hnode)->value == 1337); test(mm_heap_size(&h) == 0); test(mm_heap_min(&h) == NULL); test(mm_heap_pop(&h) == NULL); } static void test_push_pop_some(void) { mm_heap_t h; mm_heap_init(&h, cmp); el_t el[10]; for (int i = (int)lengthof(el) - 1; i >= 0; --i) { el[i].value = i; mm_heap_push(&h, &el[i].hnode); } test(mm_heap_validate(&h)); for (int i = 0; i < (int)lengthof(el); ++i) { el_t *e = mm_container_of(mm_heap_pop(&h), el_t, hnode); test(e->value == i); } test(mm_heap_size(&h) == 0); test(mm_heap_min(&h) == NULL); } static void test_remove_middle_with_right_child(void) { mm_heap_t h; mm_heap_init(&h, cmp); el_t el[7]; for (int i = 0; i < 7; ++i) { el[i].value = i; mm_heap_push(&h, &el[i].hnode); } mm_heap_remove(&h, &el[1].hnode); test(mm_heap_size(&h) == 6); test(mm_heap_validate(&h)); int expected[] = { 0, 2, 3, 4, 5, 6 }; for (int i = 0; i < 6; ++i) { el_t *e = mm_container_of(mm_heap_pop(&h), el_t, hnode); test(e->value == expected[i]); } } static void test_random(void) { mm_heap_t h; mm_heap_init(&h, cmp); enum { N = 500 }; el_t el[N]; int alive[N]; memset(alive, 0, sizeof(alive)); srand(42); for (int iter = 0; iter < 20000; ++iter) { int op = rand() % 2; int idx = rand() % N; if (op == 0 && !alive[idx]) { el[idx].value = rand() % 1000; mm_heap_push(&h, &el[idx].hnode); alive[idx] = 1; } else if (alive[idx]) { mm_heap_remove(&h, &el[idx].hnode); alive[idx] = 0; } test(mm_heap_validate(&h)); } } void machinarium_test_heap(void) { machinarium_init(); test_min_pop_empty(); test_push_pop(); test_push_min(); test_push_pop_some(); test_remove_middle_with_right_child(); test_random(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_hm_hash.c000066400000000000000000000061141526064202200236350ustar00rootroot00000000000000#include #include #include static void test_xxh64(void) { test(mm_xxh64_hash("", 0, 0) == 0xef46db3751d8e999); test(mm_xxh64_hash("", 0, 1337) == 0x7cefd03d4bb7d3f); test(mm_xxh64_hash("a", 1, 15) == 0x4c640756137de265); test(mm_xxh64_hash("b", 1, 15) == 0xfdb53a5b73c5c573); test(mm_xxh64_hash("c", 1, 15) == 0x99bb220f85650229); test(mm_xxh64_hash("\0", 1, 123234) == 0x80fe26b35712a035); test(mm_xxh64_hash("ab", 2, 0) == 0x65f708ca92d04a61); test(mm_xxh64_hash("\x7\xc", 2, 0) == 0x8bb7ba5b0c790ea0); test(mm_xxh64_hash("have you seen my coffee cup?", sizeof("have you seen my coffee cup?") - 1, 0) == 0xb9d9779043c0da27); test(mm_xxh64_hash("have you seen my coffee cup?", sizeof("have you seen my coffee cup?") - 1, 8989898) == 0x48fb8fd6f7af1212); const char *text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; test(mm_xxh64_hash(text, strlen(text), 0) == 0xc5a8b11443765630); test(mm_xxh64_hash(text, strlen(text), 15) == 0xc984e937d2ca5685); test(mm_xxh64_hash("\x61", 1, 0) == 0xd24ec4f1a98c6e5bULL); test(mm_xxh64_hash("\x68\x65\x6c\x6c\x6f", 5, 0) == 0x26c7827d889f6da3ULL); test(mm_xxh64_hash("\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64", 11, 0) == 0x45ab6734b21e6968ULL); test(mm_xxh64_hash( "\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78", 31, 0) == 0x60dd0d01083b99f0ULL); test(mm_xxh64_hash( "\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78", 32, 0) == 0xe2df261fc2ec30ebULL); test(mm_xxh64_hash( "\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78", 33, 0) == 0xb3fa465f554208a6ULL); test(mm_xxh64_hash("\x68\x65\x6c\x6c\x6f", 5, 3735928559) == 0x0c5badaa04926dcdULL); test(mm_xxh64_hash("\x68\x65\x6c\x6c\x6f", 5, 1311768467463790320) == 0xf9d3f0970dab1e3eULL); test(mm_xxh64_hash("\x00", 1, 0) == 0xe934a84adb052768ULL); test(mm_xxh64_hash("\x00\x01\x02\x03", 4, 0) == 0xffced8604453cc1eULL); test(mm_xxh64_hash( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", 64, 0) == 0x2e54fae2959dbb84ULL); test(mm_xxh64_hash("\x00", 1, 123234) == 0x80fe26b35712a035ULL); } void machinarium_test_hm_hash(void) { machinarium_init(); test_xxh64(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_init.c000066400000000000000000000002261526064202200231670ustar00rootroot00000000000000 #include #include void machinarium_test_init(void) { machinarium_init(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_io_new.c000066400000000000000000000006501526064202200235050ustar00rootroot00000000000000 #include #include #include static void coroutine(void *arg) { (void)arg; mm_io_t *io = mm_io_create(); test(io != NULL); mm_io_free(io); } void machinarium_test_io_new(void) { machinarium_init(); int id; id = machine_create("test", coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_join.c000066400000000000000000000013501526064202200231620ustar00rootroot00000000000000 #include #include static void test_child_a(void *arg) { (void)arg; machine_sleep(100); } static void test_child_b(void *arg) { (void)arg; machine_sleep(300); } static void test_waiter(void *arg) { (void)arg; int64_t a, b; b = machine_coroutine_create(test_child_b, NULL); test(b != -1); a = machine_coroutine_create(test_child_a, NULL); test(a != -1); int rc; rc = machine_join(a); test(rc == 0); rc = machine_join(b); test(rc == 0); machine_stop_current(); } void machinarium_test_join(void) { machinarium_init(); int id; id = machine_create("test", test_waiter, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_lf_stack.c000066400000000000000000000104241526064202200240130ustar00rootroot00000000000000#include #include #include #include #include #include #include typedef struct { mm_lf_stack_entry_t link; int value; } el_t; static void test_init_destroy(void) { mm_lf_stack_t st; mm_lf_stack_init(&st); test(mm_lf_stack_pop(&st) == NULL); mm_lf_stack_destroy(&st); } static void test_push_pop(void) { mm_lf_stack_t st; mm_lf_stack_init(&st); el_t el; el.value = 1337; mm_lf_stack_push(&st, &el.link); mm_lf_stack_entry_t *e = mm_lf_stack_pop(&st); test(e == &el.link); test(mm_container_of(e, el_t, link)->value == 1337); test(mm_lf_stack_pop(&st) == NULL); mm_lf_stack_destroy(&st); } static void test_lifo_order(void) { mm_lf_stack_t st; mm_lf_stack_init(&st); enum { N = 100 }; el_t el[N]; for (int i = 0; i < (int)lengthof(el); i++) { el[i].value = i; mm_lf_stack_push(&st, &el[i].link); } for (int i = (int)lengthof(el) - 1; i >= 0; i--) { mm_lf_stack_entry_t *e = mm_lf_stack_pop(&st); test(e == &el[i].link); test(mm_container_of(e, el_t, link)->value == i); } test(mm_lf_stack_pop(&st) == NULL); mm_lf_stack_destroy(&st); } static void test_reuse(void) { mm_lf_stack_t st; mm_lf_stack_init(&st); el_t el; el.value = 1; for (int i = 0; i < 1000; i++) { mm_lf_stack_push(&st, &el.link); mm_lf_stack_entry_t *e = mm_lf_stack_pop(&st); test(e == &el.link); } test(mm_lf_stack_pop(&st) == NULL); mm_lf_stack_destroy(&st); } /* concurrent push stress test. * * each thread has a private pool of elements and pushes them all * concurrently. after all threads join, the main thread drains the * stack and verifies the count. */ #define CONC_NTHREADS 8 #define CONC_NPOOL 1000 typedef struct { mm_lf_stack_t *st; el_t pool[CONC_NPOOL]; int thread_id; } conc_arg_t; static void *conc_worker(void *arg) { conc_arg_t *a = arg; for (int i = 0; i < CONC_NPOOL; i++) { a->pool[i].value = a->thread_id * CONC_NPOOL + i; mm_lf_stack_push(a->st, &a->pool[i].link); } return NULL; } static void test_concurrent(void) { mm_lf_stack_t st; mm_lf_stack_init(&st); conc_arg_t args[CONC_NTHREADS]; memset(args, 0, sizeof(args)); for (int i = 0; i < CONC_NTHREADS; i++) { args[i].st = &st; } pthread_t threads[CONC_NTHREADS]; for (int i = 0; i < CONC_NTHREADS; i++) { args[i].thread_id = i; pthread_create(&threads[i], NULL, conc_worker, &args[i]); } for (int i = 0; i < CONC_NTHREADS; i++) { pthread_join(threads[i], NULL); } /* drain remaining and count */ int remaining = 0; while (mm_lf_stack_pop(&st) != NULL) { remaining++; } test(remaining == CONC_NTHREADS * CONC_NPOOL); mm_lf_stack_destroy(&st); } /* concurrent push+pop: threads pop and push back, simulating a real * object pool (take object, use, return). with tagged pointers this * is safe from ABA. */ #define MIX_NTHREADS 4 #define MIX_NPOOL 256 #define MIX_ITERS 100000 typedef struct { mm_lf_stack_t *st; el_t pool[MIX_NPOOL]; int thread_id; } mix_arg_t; static void *mix_worker(void *arg) { mix_arg_t *a = arg; mm_lf_stack_entry_t *held = NULL; for (int i = 0; i < MIX_NPOOL; i++) { a->pool[i].value = a->thread_id * MIX_NPOOL + i; mm_lf_stack_push(a->st, &a->pool[i].link); } for (int i = 0; i < MIX_ITERS; i++) { mm_lf_stack_entry_t *e = mm_lf_stack_pop(a->st); if (e) { if (held) { mm_lf_stack_push(a->st, held); } held = e; } } if (held) { mm_lf_stack_push(a->st, held); } return NULL; } static void test_concurrent_mix(void) { mm_lf_stack_t st; mm_lf_stack_init(&st); mix_arg_t args[MIX_NTHREADS]; memset(args, 0, sizeof(args)); for (int i = 0; i < MIX_NTHREADS; i++) { args[i].st = &st; args[i].thread_id = i; } pthread_t threads[MIX_NTHREADS]; for (int i = 0; i < MIX_NTHREADS; i++) { pthread_create(&threads[i], NULL, mix_worker, &args[i]); } for (int i = 0; i < MIX_NTHREADS; i++) { pthread_join(threads[i], NULL); } int remaining = 0; while (mm_lf_stack_pop(&st) != NULL) { remaining++; } test(remaining == MIX_NTHREADS * MIX_NPOOL); mm_lf_stack_destroy(&st); } void machinarium_test_lf_stack(void) { machinarium_init(); test_init_destroy(); test_push_pop(); test_lifo_order(); test_reuse(); test_concurrent(); test_concurrent_mix(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_mutex_coroutines.c000066400000000000000000000031701526064202200256410ustar00rootroot00000000000000 #include #include #include #include #include typedef struct { uint64_t *counter; mm_mutex_t *mutex; machine_wait_group_t *wg; } incrementer_arg_t; static inline void incrementer(void *arg) { incrementer_arg_t *iarg = arg; for (int i = 0; i < (1 << 22); ++i) { test(mm_mutex_lock(iarg->mutex, 1000) == 1); (*iarg->counter)++; mm_mutex_unlock(iarg->mutex); if (i % 100 == 0) { machine_sleep(0); } } machine_wait_group_done(iarg->wg); } static inline void test_coroutines_access(void *a) { (void)a; uint64_t counter = 0; mm_mutex_t *mutex = mm_mutex_create(); test(mutex != NULL); machine_wait_group_t *wg = machine_wait_group_create(); test(wg != NULL); incrementer_arg_t arg = { .counter = &counter, .mutex = mutex, .wg = wg }; machine_wait_group_add(wg); int id1 = machine_coroutine_create(incrementer, &arg); test(id1 != -1); machine_wait_group_add(wg); int id2 = machine_coroutine_create(incrementer, &arg); test(id2 != -1); machine_wait_group_add(wg); int id3 = machine_coroutine_create(incrementer, &arg); test(id3 != -1); machine_wait_group_add(wg); int id4 = machine_coroutine_create(incrementer, &arg); test(id4 != -1); test(machine_wait_group_wait(wg, UINT32_MAX) == 0); test(counter == (4L << 22)); mm_mutex_free(mutex); } void machinarium_test_mutex_coroutines(void) { machinarium_init(); int id; id = machine_create("test_mutex_coroutines_access", test_coroutines_access, NULL); test(id != -1); test(machine_wait(id) != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_mutex_threads.c000066400000000000000000000032111526064202200250750ustar00rootroot00000000000000 #include #include #include #include #include typedef struct { uint64_t *counter; mm_mutex_t *mutex; machine_wait_group_t *wg; } incrementer_arg_t; static inline void incrementer(void *arg) { incrementer_arg_t *iarg = arg; for (int i = 0; i < (1 << 16); ++i) { test(mm_mutex_lock(iarg->mutex, 1000) == 1); (*iarg->counter)++; mm_mutex_unlock(iarg->mutex); } machine_wait_group_done(iarg->wg); } static inline void test_threads_access(void *a) { (void)a; uint64_t counter = 0; mm_mutex_t *mutex = mm_mutex_create(); test(mutex != NULL); machine_wait_group_t *wg = machine_wait_group_create(); test(wg != NULL); incrementer_arg_t arg = { .counter = &counter, .mutex = mutex, .wg = wg }; machine_wait_group_add(wg); int id1 = machine_create("test1", incrementer, &arg); test(id1 != -1); machine_wait_group_add(wg); int id2 = machine_create("test2", incrementer, &arg); test(id2 != -1); machine_wait_group_add(wg); int id3 = machine_create("test3", incrementer, &arg); test(id3 != -1); machine_wait_group_add(wg); int id4 = machine_create("test4", incrementer, &arg); test(id4 != -1); test(machine_wait_group_wait(wg, UINT32_MAX) == 0); test(counter == (4L << 16)); machine_wait(id1); machine_wait(id2); machine_wait(id3); machine_wait(id4); mm_mutex_free(mutex); } void machinarium_test_mutex_threads(void) { machinarium_init(); int id; id = machine_create("test_mutex_threads_access", test_threads_access, NULL); test(id != -1); test(machine_wait(id) != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_mutex_timeout.c000066400000000000000000000020711526064202200251340ustar00rootroot00000000000000 #include #include #include #include #include static inline void slow_task(void *arg) { mm_mutex_t *mutex = arg; test(mm_mutex_lock(mutex, 100) == 1); machine_sleep(1000); mm_mutex_unlock(mutex); } static inline void timeouted_task(void *arg) { /* ensure slow task held mutex */ machine_sleep(100); mm_mutex_t *mutex = arg; test(mm_mutex_lock(mutex, 500) == 0); } static inline void test_mutex_timeout(void *a) { (void)a; mm_mutex_t *mutex = mm_mutex_create(); test(mutex != NULL); int id1 = machine_create("slow", slow_task, mutex); test(id1 != -1); int id2 = machine_create("timeouted_task", timeouted_task, mutex); test(id2 != -1); test(machine_wait(id2) != -1); test(machine_wait(id1) != -1); mm_mutex_free(mutex); } void machinarium_test_mutex_timeout(void) { machinarium_init(); int id; id = machine_create("test_mutex_timeout", test_mutex_timeout, NULL); test(id != -1); test(machine_wait(id) != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_producer_consumer0.c000066400000000000000000000020501526064202200260370ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static int producer; static int consumer; static void test_consumer(void *arg) { (void)arg; int i = 0; for (; i < 100; i++) { machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); machine_msg_free(msg); } } static void test_producer(void *arg) { (void)arg; int i = 0; for (; i < 100; i++) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, i); machine_channel_write(channel, msg); } } void machinarium_test_producer_consumer0(void) { machinarium_init(); channel = machine_channel_create(); test(channel != NULL); producer = machine_create("producer", test_producer, NULL); test(producer != -1); consumer = machine_create("consumer", test_consumer, NULL); test(consumer != -1); int rc; rc = machine_wait(consumer); test(rc != -1); rc = machine_wait(producer); test(rc != -1); machine_channel_free(channel); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_producer_consumer1.c000066400000000000000000000033071526064202200260460ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static int consumers_count = 5; static int consumers_stat[5] = { 0 }; static void test_consumer(void *arg) { uintptr_t consumer_id = (uintptr_t)arg; for (;;) { machine_msg_t *msg; msg = machine_channel_read(channel, UINT32_MAX); test(msg != NULL); consumers_stat[consumer_id]++; int is_exit = (uint32_t)machine_msg_type(msg) == UINT32_MAX; machine_msg_free(msg); if (is_exit) { break; } } } static void test_producer(void *arg) { (void)arg; int i = 0; for (; i < 100000; i++) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, i); machine_channel_write(channel, msg); } /* exit */ for (i = 0; i < consumers_count; i++) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, UINT32_MAX); machine_channel_write(channel, msg); } } void machinarium_test_producer_consumer1(void) { machinarium_init(); channel = machine_channel_create(); test(channel != NULL); int producer; producer = machine_create("producer", test_producer, NULL); test(producer != -1); int consumers[consumers_count]; uintptr_t i = 0; for (; (int)i < consumers_count; i++) { consumers[i] = machine_create("consumer", test_consumer, (void *)i); test(consumers[i] != -1); } int rc; rc = machine_wait(producer); test(rc != -1); printf("["); for (i = 0; (int)i < consumers_count; i++) { rc = machine_wait(consumers[i]); test(rc != -1); if (i > 0) { printf(", "); } printf("%d", consumers_stat[i]); fflush(NULL); } printf("] "); machine_channel_free(channel); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_producer_consumer2.c000066400000000000000000000022321526064202200260430ustar00rootroot00000000000000 #include #include static machine_channel_t *channel; static int count_written = 0; static int count_read = 0; static int pc; static void test_consumer(void *arg) { (void)arg; while (count_read < count_written) { machine_msg_t *msg; msg = machine_channel_read(channel, 0); if (msg == NULL) { break; } machine_msg_free(msg); count_read++; machine_sleep(0); } } static void test_pc(void *arg) { (void)arg; int i = 0; for (; i < 1000; i++) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); machine_msg_set_type(msg, i); count_written++; machine_channel_write(channel, msg); } for (i = 0; i < 100; i++) { int rc = machine_coroutine_create(test_consumer, NULL); test(rc != -1); } } void machinarium_test_producer_consumer2(void) { machinarium_init(); channel = machine_channel_create(); test(channel != NULL); pc = machine_create("producer-consumer", test_pc, NULL); test(pc != -1); int rc; rc = machine_wait(pc); test(rc != -1); test(count_read == 1000); test(count_written == count_read); machine_channel_free(channel); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_queue.c000066400000000000000000000111551526064202200233530ustar00rootroot00000000000000#include #include #include #include static void test_init_destroy(void) { mm_queue_t q; mm_queue_init(&q, 10, sizeof(int), NULL); mm_queue_destroy(&q); } static void test_push_pop(void) { mm_queue_t q; mm_queue_init(&q, 10, sizeof(int), NULL); int val = 5; test(mm_queue_push(&q, &val) == 1); val = 42; test(mm_queue_pop(&q, &val) == 1); test(val == 5); mm_queue_destroy(&q); } static void test_push_full(void) { mm_queue_t q; mm_queue_init(&q, 4, sizeof(int), NULL); for (int i = 0; i < 4; i++) { test(mm_queue_push(&q, &i) == 1); } int val = 99; test(mm_queue_push(&q, &val) == 0); test(mm_queue_size(&q) == 4); mm_queue_destroy(&q); } static void test_pop_empty(void) { mm_queue_t q; mm_queue_init(&q, 4, sizeof(int), NULL); int val = 99; test(mm_queue_pop(&q, &val) == 0); test(val == 99); mm_queue_destroy(&q); } static void test_fifo_order(void) { mm_queue_t q; mm_queue_init(&q, 8, sizeof(int), NULL); for (int i = 0; i < 8; i++) { test(mm_queue_push(&q, &i) == 1); } for (int i = 0; i < 8; i++) { int val; test(mm_queue_pop(&q, &val) == 1); test(val == i); } mm_queue_destroy(&q); } static void test_wraparound(void) { mm_queue_t q; mm_queue_init(&q, 4, sizeof(int), NULL); for (int i = 0; i < 4; i++) { test(mm_queue_push(&q, &i) == 1); } for (int i = 0; i < 2; i++) { int val; test(mm_queue_pop(&q, &val) == 1); test(val == i); } for (int i = 4; i < 6; i++) { test(mm_queue_push(&q, &i) == 1); } int expected[] = { 2, 3, 4, 5 }; for (int i = 0; i < 4; i++) { int val; test(mm_queue_pop(&q, &val) == 1); test(val == expected[i]); } test(mm_queue_size(&q) == 0); mm_queue_destroy(&q); } static void test_pop_batch_simple(void) { mm_queue_t q; mm_queue_init(&q, 8, sizeof(int), NULL); for (int i = 0; i < 5; i++) { test(mm_queue_push(&q, &i) == 1); } int dst[8]; size_t n = mm_queue_pop_batch(&q, dst, 8); test(n == 5); for (int i = 0; i < 5; i++) { test(dst[i] == i); } size_t s = mm_queue_size(&q); test(s == 0); mm_queue_destroy(&q); } static void test_pop_batch_wraparound(void) { mm_queue_t q; mm_queue_init(&q, 4, sizeof(int), NULL); for (int i = 0; i < 4; i++) { test(mm_queue_push(&q, &i) == 1); } for (int i = 0; i < 2; i++) { int val; mm_queue_pop(&q, &val); } for (int i = 4; i < 6; i++) { test(mm_queue_push(&q, &i) == 1); } int dst[4]; size_t n = mm_queue_pop_batch(&q, dst, 4); test(n == 4); int expected[] = { 2, 3, 4, 5 }; for (int i = 0; i < 4; i++) { test(dst[i] == expected[i]); } mm_queue_destroy(&q); } static void test_pop_batch_limit(void) { mm_queue_t q; mm_queue_init(&q, 8, sizeof(int), NULL); for (int i = 0; i < 8; i++) { test(mm_queue_push(&q, &i) == 1); } int dst[4]; size_t n = mm_queue_pop_batch(&q, dst, 4); test(n == 4); test(mm_queue_size(&q) == 4); for (int i = 0; i < 4; i++) { test(dst[i] == i); } mm_queue_destroy(&q); } int dtor_count = 0; static void my_dtor(void *l) { ++dtor_count; od_free(*(void **)l); } static void test_dtor(void) { mm_queue_t q; mm_queue_init(&q, 4, sizeof(int *), my_dtor); for (int i = 0; i < 3; i++) { int *val = od_malloc(sizeof(int)); *val = i; test(mm_queue_push(&q, &val) == 1); } mm_queue_destroy(&q); test(dtor_count == 3); } typedef struct { mm_queue_t *q; int count; } thread_arg_t; static void producer_coroutine(void *arg) { thread_arg_t *a = arg; for (int i = 0; i < a->count; i++) { while (!mm_queue_push(a->q, &i)) { machine_sleep(0); } } } static atomic_uint_fast64_t consumed; static void consumer_coroutine(void *arg) { atomic_init(&consumed, 0); thread_arg_t *a = arg; int prev = -1; for (int i = 0; i < a->count; i++) { int val; while (!mm_queue_pop(a->q, &val)) { machine_sleep(0); } test(val == prev + 1); prev = val; atomic_fetch_add(&consumed, 1); } } static void test_concurrent(void) { mm_queue_t q; mm_queue_init(&q, 16, sizeof(int), NULL); thread_arg_t arg = { .q = &q, .count = 1000000 }; int64_t m1 = machine_create("producer", producer_coroutine, &arg); test(m1 != -1); int64_t m2 = machine_create("consumer", consumer_coroutine, &arg); test(m2 != -1); machine_wait(m1); machine_wait(m2); uint64_t c = atomic_load(&consumed); test(c == 1000000); mm_queue_destroy(&q); } void machinarium_test_queue(void) { machinarium_init(); test_init_destroy(); test_push_pop(); test_push_full(); test_pop_empty(); test_fifo_order(); test_wraparound(); test_pop_batch_simple(); test_pop_batch_wraparound(); test_pop_batch_limit(); test_dtor(); test_concurrent(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_read_10mb0.c000066400000000000000000000043461526064202200240450ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); int chunk_size = 10 * 1024; int total = 10 * 1024 * 1024; int pos = 0; while (pos < total) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_size); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_size); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); pos += chunk_size; } /* wait for disconnection */ machine_msg_t *msg = machine_read(client, 1, UINT32_MAX); test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); int pos = 0; while (pos < 10 * 1024 * 1024) { machine_msg_t *msg; msg = machine_read(client, 1024, UINT32_MAX); if (msg == NULL) { break; } test(machine_msg_size(msg) == 1024); machine_msg_free(msg); pos += 1024; } test(pos == 10 * 1024 * 1024); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_read_10mb0(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_read_10mb1.c000066400000000000000000000042551526064202200240450ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); int chunk_size = 10 * 1024; int total = 10 * 1024 * 1024; int pos = 0; while (pos < total) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_size); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_size); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); pos += chunk_size; } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 10 * 1024 * 1024, UINT32_MAX); test(msg != NULL); char *buf_cmp = malloc(10 * 1024 * 1024); test(buf_cmp != NULL); memset(buf_cmp, 'x', 10 * 1024 * 1024); test(memcmp(buf_cmp, machine_msg_data(msg), 10 * 1024 * 1024) == 0); free(buf_cmp); machine_msg_free(msg); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_read_10mb1(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_read_10mb2.c000066400000000000000000000040711526064202200240420ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, 10 * 1024 * 1024); test(rc == 0); memset(machine_msg_data(msg), 'x', 10 * 1024 * 1024); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 10 * 1024 * 1024, UINT32_MAX); test(msg != NULL); char *buf_cmp = malloc(10 * 1024 * 1024); test(buf_cmp != NULL); memset(buf_cmp, 'x', 10 * 1024 * 1024); test(memcmp(buf_cmp, machine_msg_data(msg), 10 * 1024 * 1024) == 0); free(buf_cmp); machine_msg_free(msg); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_read_10mb2(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_read_cancel.c000066400000000000000000000033611526064202200244470ustar00rootroot00000000000000 #include #include #include #include #include #include #include static int client_id; static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_sleep(0); rc = machine_cancel(client_id); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 12, UINT32_MAX); test(msg == NULL); test(!machine_timedout()); test(machine_cancelled()); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); client_id = rc; } void machinarium_test_read_cancel(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_read_timeout.c000066400000000000000000000031701526064202200247060ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_sleep(1000); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_msg_t *msg; msg = machine_read(client, 12, 500); test(msg == NULL); test(machine_timedout()); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_read_timeout(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_read_var.c000066400000000000000000000051701526064202200240120ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); mm_io_set_nodelay(client, 1); int chunk_size = 100 * 1024; int chunk_pos = 90 * 1024; while (chunk_pos < chunk_size) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_pos); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_pos); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); /* ack */ msg = machine_read(client, sizeof(uint32_t), UINT32_MAX); test(msg != NULL); machine_msg_free(msg); chunk_pos++; } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); mm_io_set_nodelay(client, 1); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); int chunk_size = 100 * 1024; char *chunk_cmp = malloc(chunk_size); test(chunk_cmp != NULL); memset(chunk_cmp, 'x', chunk_size); int chunk_pos = 90 * 1024; while (chunk_pos < chunk_size) { machine_msg_t *msg; msg = machine_read(client, chunk_pos, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), chunk_cmp, chunk_pos) == 0); machine_msg_free(msg); msg = machine_msg_create(0); uint32_t ack = 1; rc = machine_msg_write(msg, (void *)&ack, sizeof(ack)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); chunk_pos++; } free(chunk_cmp); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_read_var(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_ring_buffer.c000066400000000000000000000077461526064202200245320ustar00rootroot00000000000000#include #include void machinarium_test_ring_buffer(void) { machinarium_init(); uint8_t buff[256]; machine_ring_buffer_t *rbuf = machine_ring_buffer_create(10); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 0); test(machine_ring_buffer_available(rbuf) == 10); test(machine_ring_buffer_read(rbuf, buff, 10) == 0); test(machine_ring_buffer_write(rbuf, "aaaaa", 5) == 5); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 5); test(machine_ring_buffer_available(rbuf) == 5); test(machine_ring_buffer_write(rbuf, "bbbbb", 4) == 4); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 9); test(machine_ring_buffer_available(rbuf) == 1); test(machine_ring_buffer_write(rbuf, "ccccc", 5) == 1); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 10); test(machine_ring_buffer_available(rbuf) == 0); test(machine_ring_buffer_write(rbuf, "ddddd", 5) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 10); test(machine_ring_buffer_available(rbuf) == 0); test(machine_ring_buffer_read(rbuf, buff, 5) == 5); test(memcmp(buff, "aaaaa", 5) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 5); test(machine_ring_buffer_available(rbuf) == 5); test(machine_ring_buffer_read(rbuf, buff + 5, 4) == 4); test(memcmp(buff, "aaaaabbbb", 9) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 1); test(machine_ring_buffer_available(rbuf) == 9); test(machine_ring_buffer_read(rbuf, buff + 9, 1) == 1); test(memcmp(buff, "aaaaabbbbc", 10) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 0); test(machine_ring_buffer_available(rbuf) == 10); test(machine_ring_buffer_read(rbuf, buff, 5) == 0); test(memcmp(buff, "aaaaabbbbc", 10) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 0); test(machine_ring_buffer_available(rbuf) == 10); test(machine_ring_buffer_write(rbuf, "eeeeeeeeeeeeeee", 15) == 10); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 10); test(machine_ring_buffer_available(rbuf) == 0); test(machine_ring_buffer_read(rbuf, buff, 10) == 10); test(memcmp(buff, "eeeeeeeeee", 10) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 0); test(machine_ring_buffer_available(rbuf) == 10); test(machine_ring_buffer_write(rbuf, "ffffffff", 8) == 8); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 8); test(machine_ring_buffer_available(rbuf) == 2); test(machine_ring_buffer_read(rbuf, buff, 8) == 8); test(memcmp(buff, "ffffffff", 8) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 0); test(machine_ring_buffer_available(rbuf) == 10); test(machine_ring_buffer_write(rbuf, "kkkkkkkk", 8) == 8); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 8); test(machine_ring_buffer_available(rbuf) == 2); test(machine_ring_buffer_read(rbuf, buff, 8) == 8); test(memcmp(buff, "kkkkkkkk", 8) == 0); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 0); test(machine_ring_buffer_available(rbuf) == 10); test(machine_ring_buffer_write(rbuf, "aaabbbcccd", 10) == 10); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 10); test(machine_ring_buffer_available(rbuf) == 0); test(machine_ring_buffer_drain(rbuf, 3) == 3); test(machine_ring_buffer_capacity(rbuf) == 10); test(machine_ring_buffer_size(rbuf) == 7); test(machine_ring_buffer_available(rbuf) == 3); test(machine_ring_buffer_read(rbuf, buff, 7) == 7); test(memcmp(buff, "bbbcccd", 7) == 0); machine_ring_buffer_free(rbuf); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_sem.c000066400000000000000000000030671526064202200230160ustar00rootroot00000000000000#include #include #include #include #define NUM_THREADS 10 static atomic_uint_fast64_t concurrency; static atomic_uint_fast64_t result; static void worker(void *a) { mm_sem_t *sem = a; for (int i = 0; i < 1000000; ++i) { mm_sem_wait(sem); uint64_t r = atomic_fetch_add(&concurrency, 1); if (r > 1) { atomic_store(&result, 1); } test(r <= 3); atomic_fetch_sub(&concurrency, 1); mm_sem_post(sem); } } static void do_test(void *arg) { (void)arg; mm_sem_t sem; mm_sem_init(&sem, 4); atomic_init(&concurrency, 0); atomic_init(&result, 0); int64_t workers[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; ++i) { char name[10]; snprintf(name, sizeof(name), "worker %d", i); workers[i] = machine_create(name, worker, &sem); test(workers[i] != -1); } for (int i = 0; i < NUM_THREADS; ++i) { machine_wait(workers[i]); } test(atomic_load(&result) == 1); mm_sem_destroy(&sem); } static void do_timeout_test(void *arg) { (void)arg; mm_sem_t sem; mm_sem_init(&sem, 0); int rc = mm_sem_timedwait(&sem, 500); test(rc == -1); test(mm_errno_get() == ETIMEDOUT); mm_sem_destroy(&sem); } void machinarium_test_sem(void) { machinarium_init(); int id; id = machine_create("machinarium_test_sem", do_test, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); id = machine_create("machinarium_test_sem_timeout", do_timeout_test, NULL); test(id != -1); rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_signal0.c000066400000000000000000000014361526064202200235650ustar00rootroot00000000000000 #include #include #include #include static void coroutine(void *arg) { (void)arg; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigset_t ignore; sigemptyset(&ignore); int rc; rc = machine_signal_init(&mask, &ignore); test(rc == 0); rc = kill(getpid(), SIGINT); test(rc == 0); rc = machine_signal_wait(UINT32_MAX); test(rc == SIGINT); } void machinarium_test_signal0(void) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigprocmask(SIG_BLOCK, &mask, NULL); machinarium_init(); int id; id = machine_create("test", coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); sigprocmask(SIG_UNBLOCK, &mask, NULL); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_signal1.c000066400000000000000000000015201526064202200235600ustar00rootroot00000000000000 #include #include #include #include static void coroutine(void *arg) { (void)arg; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigset_t ignore; sigemptyset(&ignore); int rc; rc = machine_signal_init(&mask, &ignore); test(rc == 0); rc = kill(getpid(), SIGINT); test(rc == 0); rc = machine_signal_wait(UINT32_MAX); test(rc == SIGINT); rc = machine_signal_wait(100); test(rc == -1); } void machinarium_test_signal1(void) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigprocmask(SIG_BLOCK, &mask, NULL); machinarium_init(); int id; id = machine_create("test", coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); sigprocmask(SIG_UNBLOCK, &mask, NULL); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_sleep.c000066400000000000000000000015341526064202200233370ustar00rootroot00000000000000 #include #include static void coroutine(void *arg) { (void)arg; machine_sleep(100); machine_stop_current(); } void machinarium_test_sleep(void) { machinarium_init(); int id; id = machine_create("test", coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } static void coroutine_random_sleep(void *arg) { (void)arg; long int duration = machine_lrand48(); machine_sleep(duration & 0xFF); machine_stop_current(); } void machinarium_test_sleep_random(void) { machinarium_init(); int n = 100; int id[n]; for (int i = 0; i < n; i++) { id[i] = machine_create("test", coroutine_random_sleep, NULL); test(id[i] != -1); } for (int i = 0; i < n; i++) { int rc; rc = machine_wait(id[i]); test(rc != -1); } machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_sleep_cancel0.c000066400000000000000000000013051526064202200247200ustar00rootroot00000000000000 #include #include static void test_sleep_cancel0_child(void *arg) { (void)arg; machine_sleep(6000000); test(machine_cancelled()) } static void test_sleep_cancel0_parent(void *arg) { (void)arg; int64_t id; id = machine_coroutine_create(test_sleep_cancel0_child, NULL); test(id != -1); mm_yield; int rc; rc = machine_cancel(id); test(rc == 0); rc = machine_join(id); test(rc == 0); machine_stop_current(); } void machinarium_test_sleep_cancel0(void) { machinarium_init(); int id; id = machine_create("test", test_sleep_cancel0_parent, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_sleep_yield.c000066400000000000000000000005711526064202200245250ustar00rootroot00000000000000 #include #include static void coroutine(void *arg) { (void)arg; machine_sleep(0); machine_stop_current(); } void machinarium_test_sleep_yield(void) { machinarium_init(); int id; id = machine_create("test", coroutine, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_sleeplock.c000066400000000000000000000017241526064202200242110ustar00rootroot00000000000000 #include #include #include #include mm_sleeplock_t global_lock; static void test_coroutine(void *arg) { uint64_t *value = (uint64_t *)arg; for (int i = 0; i < (1 << 22); i++) { mm_sleeplock_lock(&global_lock); (*value)++; mm_sleeplock_unlock(&global_lock); } } void machinarium_test_sleeplock(void) { machinarium_init(); uint64_t value = 0; mm_sleeplock_init(&global_lock); int id0, id1, id2, id3; id0 = machine_create("test", test_coroutine, &value); id1 = machine_create("test", test_coroutine, &value); id2 = machine_create("test", test_coroutine, &value); id3 = machine_create("test", test_coroutine, &value); test(id0 != -1); test(id1 != -1); test(id2 != -1); test(id3 != -1); test(machine_wait(id0) != -1); test(machine_wait(id1) != -1); test(machine_wait(id2) != -1); test(machine_wait(id3) != -1); test(value == (4L << 22)); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_stat.c000066400000000000000000000034521526064202200232030ustar00rootroot00000000000000 #include #include #include #if 0 static void coroutine(void *arg) { (void)arg; machine_sleep(100); } #endif void machinarium_test_stat(void) { machinarium_init(); #if 0 uint64_t count_machine = 0; uint64_t count_coroutine = 0; uint64_t count_coroutine_cache = 0; uint64_t msg_allocated = 0; uint64_t msg_cache_count = 0; uint64_t msg_cache_gc_count = 0; uint64_t msg_cache_size = 0; for (;;) { machinarium_stat(&count_machine, &count_coroutine, &count_coroutine_cache, &msg_allocated, &msg_cache_count, &msg_cache_gc_count, &msg_cache_size); test(count_machine == 3); /* thread pool */ test(count_coroutine_cache == 0); if (count_coroutine != 3) { usleep(10000); continue; } break; } int id; id = machine_create("test", coroutine, NULL); test(id != -1); for (;;) { machinarium_stat(&count_machine, &count_coroutine, &count_coroutine_cache, &msg_allocated, &msg_cache_count, &msg_cache_gc_count, &msg_cache_size); test(count_machine == 3 + 1); test(count_coroutine_cache == 0); if (count_coroutine != 4) { usleep(10000); continue; } break; } int rc; rc = machine_wait(id); test(rc != -1); for (;;) { machinarium_stat(&count_machine, &count_coroutine, &count_coroutine_cache, &msg_allocated, &msg_cache_count, &msg_cache_gc_count, &msg_cache_size); test(count_machine == 3) if (count_coroutine != 3) { usleep(10000); continue; } test(count_coroutine_cache == 0); break; } #endif machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls0.c000066400000000000000000000057141526064202200231150ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client = NULL; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); test(client != NULL); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); char text[] = "hello world"; rc = machine_msg_write(msg, text, sizeof(text)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_read(client, 12, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "hello world", 12) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); /* eof */ test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls0(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_cache.c000066400000000000000000000047141526064202200241570ustar00rootroot00000000000000 /* * machinarium. * * TLS context cache tests: contexts are keyed by TLS options content, * so per-attempt tls objects with identical options share one context, * and machine_tls_cache_invalidate() rebuilds them. */ #include #include #include #include static void test_cache(void *arg) { (void)arg; /* two objects with identical options, like the per-attempt * backend tls objects of one storage */ machine_tls_t *tls1 = machine_tls_create(); test(tls1 != NULL); test(machine_tls_set_verify(tls1, "none") == 0); machine_tls_t *tls2 = machine_tls_create(); test(tls2 != NULL); test(machine_tls_set_verify(tls2, "none") == 0); mm_io_t *io1 = mm_io_create(); mm_io_t *io2 = mm_io_create(); test(io1 != NULL); test(io2 != NULL); io1->tls = (mm_tls_t *)tls1; io2->tls = (mm_tls_t *)tls2; SSL_CTX *ctx1 = mm_tls_get_context(io1, 1); test(ctx1 != NULL); /* * Hold a reference to ctx1: an SSL keeps the context memory * alive (refcounted by SSL_new), so a freshly created context * can never land on the same address. */ SSL *keepalive = SSL_new(ctx1); test(keepalive != NULL); SSL_CTX *ctx2 = mm_tls_get_context(io2, 1); test(ctx2 != NULL); /* same options content -> same cached context */ test(ctx1 == ctx2); /* server side uses a separate context */ SSL_CTX *ctx3 = mm_tls_get_context(io1, 0); test(ctx3 != NULL); test(ctx3 != ctx1); /* different options -> different context */ machine_tls_t *tls4 = machine_tls_create(); test(tls4 != NULL); test(machine_tls_set_verify(tls4, "none") == 0); test(machine_tls_set_ca_file(tls4, "./machinarium/ca.crt") == 0); mm_io_t *io4 = mm_io_create(); test(io4 != NULL); io4->tls = (mm_tls_t *)tls4; SSL_CTX *ctx4 = mm_tls_get_context(io4, 1); test(ctx4 != NULL); test(ctx4 != ctx1); /* invalidate: contexts are rebuilt in a new epoch */ machine_tls_cache_invalidate(); SSL_CTX *ctx5 = mm_tls_get_context(io1, 1); test(ctx5 != NULL); test(ctx5 != ctx1); SSL_CTX *ctx6 = mm_tls_get_context(io2, 1); test(ctx6 != NULL); test(ctx6 == ctx5); SSL_free(keepalive); mm_io_free(io1); mm_io_free(io2); mm_io_free(io4); machine_tls_free(tls1); machine_tls_free(tls2); machine_tls_free(tls4); } void machinarium_test_tls_cache(void) { machinarium_init(); int id; id = machine_create("test", test_cache, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_read_10mb0.c000066400000000000000000000062431526064202200247250ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } int chunk_size = 10 * 1024; int total = 10 * 1024 * 1024; int pos = 0; while (pos < total) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_size); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_size); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); pos += chunk_size; } /* wait for disconnection */ machine_msg_t *msg = machine_read(client, 1, UINT32_MAX); test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } int pos = 0; while (pos < 10 * 1024 * 1024) { machine_msg_t *msg; msg = machine_read(client, 1024, UINT32_MAX); if (msg == NULL) { break; } machine_msg_free(msg); pos += 1024; } test(pos == 10 * 1024 * 1024); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls_read_10mb0(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_read_10mb1.c000066400000000000000000000062211526064202200247220ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } int chunk_size = 10 * 1024; int total = 10 * 1024 * 1024; int pos = 0; while (pos < total) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_size); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_size); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); pos += chunk_size; } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_read(client, 10 * 1024 * 1024, UINT32_MAX); test(msg != NULL); char *buf_cmp = malloc(10 * 1024 * 1024); test(buf_cmp != NULL); memset(buf_cmp, 'x', 10 * 1024 * 1024); test(memcmp(buf_cmp, machine_msg_data(msg), 10 * 1024 * 1024) == 0); free(buf_cmp); machine_msg_free(msg); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls_read_10mb1(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_read_10mb2.c000066400000000000000000000060351526064202200247260ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, 10 * 1024 * 1024); test(rc == 0); memset(machine_msg_data(msg), 'x', 10 * 1024 * 1024); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_read(client, 10 * 1024 * 1024, UINT32_MAX); test(msg != NULL); char *buf_cmp = malloc(10 * 1024 * 1024); test(buf_cmp != NULL); memset(buf_cmp, 'x', 10 * 1024 * 1024); test(memcmp(buf_cmp, machine_msg_data(msg), 10 * 1024 * 1024) == 0); free(buf_cmp); machine_msg_free(msg); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls_read_10mb2(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_read_multithread.c000066400000000000000000000064411526064202200264300ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(*(int *)arg); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } int chunk_size = 10 * 1024; int total = 10 * 1024 * 1024; int pos = 0; while (pos < total) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_size); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_size); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); pos += chunk_size; } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); } static void client(void *arg) { mm_io_t *client = mm_io_create(); test(client != NULL); int rc; struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(*(int *)arg); rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_read(client, 10 * 1024 * 1024, UINT32_MAX); test(msg != NULL); char *buf_cmp = malloc(10 * 1024 * 1024); test(buf_cmp != NULL); memset(buf_cmp, 'x', 10 * 1024 * 1024); test(memcmp(buf_cmp, machine_msg_data(msg), 10 * 1024 * 1024) == 0); free(buf_cmp); machine_msg_free(msg); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { int rc; rc = machine_coroutine_create(server, arg); test(rc != -1); rc = machine_coroutine_create(client, arg); test(rc != -1); } void machinarium_test_tls_read_multithread(void) { machinarium_init(); const int pairs = 10; int id[10]; int port[10]; int rc; int i = 0; while (i < pairs) { port[i] = i + 7778; id[i] = machine_create("test", test_cs, &port[i]); test(id[i] != -1); i++; } i = 0; while (i < pairs) { rc = machine_wait(id[i]); test(rc != -1); i++; } machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_read_var.c000066400000000000000000000071341526064202200246760ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); mm_io_set_nodelay(client, 1); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } int chunk_size = 100 * 1024; int chunk_pos = 90 * 1024; while (chunk_pos < chunk_size) { machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); rc = machine_msg_write(msg, NULL, chunk_pos); test(rc == 0); memset(machine_msg_data(msg), 'x', chunk_pos); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); /* ack */ msg = machine_read(client, sizeof(uint32_t), UINT32_MAX); test(msg != NULL); machine_msg_free(msg); chunk_pos++; } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); mm_io_set_nodelay(client, 1); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); sa.sin_port = htons(7778); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } int chunk_size = 100 * 1024; char *chunk_cmp = malloc(chunk_size); test(chunk_cmp != NULL); memset(chunk_cmp, 'x', chunk_size); int chunk_pos = 90 * 1024; while (chunk_pos < chunk_size) { machine_msg_t *msg; msg = machine_read(client, chunk_pos, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), chunk_cmp, chunk_pos) == 0); machine_msg_free(msg); msg = machine_msg_create(0); uint32_t ack = 1; rc = machine_msg_write(msg, (void *)&ack, sizeof(ack)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); chunk_pos++; } free(chunk_cmp); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls_read_var(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_unix_socket.c000066400000000000000000000060301526064202200254400ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client = NULL; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); test(client != NULL); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_msg_create(0); test(msg != NULL); char text[] = "hello world"; rc = machine_msg_write(msg, text, sizeof(text)); test(rc == 0); rc = machine_write(client, msg, UINT32_MAX); test(rc == 0); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); unlink("_un_test"); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg; msg = machine_read(client, 12, UINT32_MAX); test(msg != NULL); test(memcmp(machine_msg_data(msg), "hello world", 12) == 0); machine_msg_free(msg); msg = machine_read(client, 1, UINT32_MAX); /* eof */ test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { unlink("_un_test"); (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls_unix_socket(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_unix_socket_no_msg.c000066400000000000000000000052321526064202200270050ustar00rootroot00000000000000 #include #include #include #include #include #include #include static void server(void *arg) { (void)arg; mm_io_t *server = mm_io_create(); test(server != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_bind(server, (struct sockaddr *)&sa, MM_BINDWITH_SO_REUSEADDR); test(rc == 0); mm_io_t *client = NULL; rc = mm_io_accept(server, &client, 16, 1, UINT32_MAX); test(rc == 0); test(client != NULL); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/server.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/server.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } rc = mm_io_close(client); test(rc == 0); mm_io_free(client); rc = mm_io_close(server); test(rc == 0); mm_io_free(server); machine_tls_free(tls); unlink("_un_test"); } static void client(void *arg) { (void)arg; mm_io_t *client = mm_io_create(); test(client != NULL); struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strncpy(sa.sun_path, "_un_test", sizeof(sa.sun_path) - 1); int rc; rc = mm_io_connect(client, (struct sockaddr *)&sa, UINT32_MAX); test(rc == 0); machine_tls_t *tls; tls = machine_tls_create(); rc = machine_tls_set_verify(tls, "none"); test(rc == 0); rc = machine_tls_set_ca_file(tls, "./machinarium/ca.crt"); test(rc == 0); rc = machine_tls_set_cert_file(tls, "./machinarium/client.crt"); test(rc == 0); rc = machine_tls_set_key_file(tls, "./machinarium/client.key"); test(rc == 0); rc = mm_io_set_tls(client, tls, UINT32_MAX); if (rc == -1) { printf("%s\n", mm_io_error(client)); test(rc == 0); } machine_msg_t *msg = machine_read(client, 1, UINT32_MAX); /* eof */ test(msg == NULL); rc = mm_io_close(client); test(rc == 0); mm_io_free(client); machine_tls_free(tls); } static void test_cs(void *arg) { unlink("_un_test"); (void)arg; int rc; rc = machine_coroutine_create(server, NULL); test(rc != -1); rc = machine_coroutine_create(client, NULL); test(rc != -1); } void machinarium_test_tls_unix_socket_no_msg(void) { machinarium_init(); int id; id = machine_create("test", test_cs, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tls_verify_error.c000066400000000000000000000054541526064202200256330ustar00rootroot00000000000000 #include #include #include #include #include static int verify_result(X509_STORE_CTX *ctx, void *arg) { X509_STORE_CTX_set_error(ctx, *(int *)arg); /* Complete the handshake so Machinarium checks the verification result. */ return 1; } static void server_handshake(void *arg) { mm_io_t *io = arg; test(mm_tls_handshake(io, 5000) == 0); } static void test_verify_error(void *arg) { (void)arg; machine_tls_t *server_tls = machine_tls_create(); machine_tls_t *client_tls = machine_tls_create(); test(server_tls != NULL); test(client_tls != NULL); test(machine_tls_set_cert_file(server_tls, "./machinarium/server.crt") == 0); test(machine_tls_set_key_file(server_tls, "./machinarium/server.key") == 0); int errors[] = { X509_V_OK, X509_V_ERR_PERMITTED_VIOLATION, X509_V_ERR_CERT_HAS_EXPIRED, X509_V_ERR_INVALID_CA }; for (size_t i = 0; i < sizeof(errors) / sizeof(errors[0]); i++) { int fds[2]; test(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); mm_io_t *server = mm_io_create(); mm_io_t *client = mm_io_create(); test(server != NULL); test(client != NULL); server->is_unix_socket = 1; client->is_unix_socket = 1; test(mm_io_socket_set(server, fds[0]) == 0); test(mm_io_socket_set(client, fds[1]) == 0); test(mm_socket_set_nonblock(fds[0], 1) == 0); test(mm_socket_set_nonblock(fds[1], 1) == 0); test(mm_io_attach(server) == 0); test(mm_io_attach(client) == 0); server->accepted = 1; server->connected = 1; client->connected = 1; server->tls = (mm_tls_t *)server_tls; client->tls = (mm_tls_t *)client_tls; SSL_CTX *ctx = mm_tls_get_context(client, 1); test(ctx != NULL); SSL_CTX_set_cert_verify_callback(ctx, verify_result, &errors[i]); int id = machine_coroutine_create(server_handshake, server); test(id != -1); int rc = mm_tls_handshake(client, 5000); test(SSL_get_verify_result(client->tls_ssl) == errors[i]); if (errors[i] == X509_V_OK) { test(rc == 0); test(client->tls_error == 0); } else { test(rc == -1); test(client->tls_error == 1); const char *error = mm_io_error(client); test(strstr(error, X509_verify_cert_error_string( errors[i])) != NULL); char code[64]; snprintf(code, sizeof(code), "SSL_get_verify_result(): %d", errors[i]); test(strstr(error, code) != NULL); } test(machine_join(id) == 0); test(mm_io_close(client) == 0); test(mm_io_close(server) == 0); mm_io_free(client); mm_io_free(server); } machine_tls_free(client_tls); machine_tls_free(server_tls); } void machinarium_test_tls_verify_error(void) { test(machinarium_init() == 0); int id = machine_create("test", test_verify_error, NULL); test(id != -1); test(machine_wait(id) == 0); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_tsan_simple_race_example.c000066400000000000000000000027741526064202200272610ustar00rootroot00000000000000#include #include #include #define NUM_COROUTINES 10 static uint64_t value = 0; static inline void nop(void *arg) { (void)arg; } static inline void producer(void *arg) { int number = *((int *)arg); value = number; } static inline void spawn_coroutines(void *arg) { size_t producer_index = *((size_t *)(arg)); int ids[NUM_COROUTINES]; int args[NUM_COROUTINES]; for (size_t i = 0; i < NUM_COROUTINES; ++i) { args[i] = i; if (i == producer_index) { ids[i] = machine_coroutine_create(producer, &args[i]); } else { ids[i] = machine_coroutine_create(nop, &args[i]); } test(ids[i] != -1); } for (size_t i = 0; i < NUM_COROUTINES; ++i) { machine_join(ids[i]); } } static inline void test_simple_race_example(void *arg) { (void)arg; int id1, id2; size_t producer_index_1 = 2; id1 = machine_create("test_tsan_simple_race_example_1", spawn_coroutines, &producer_index_1); test(id1 != -1); size_t producer_index_2 = 4; id2 = machine_create("test_tsan_simple_race_example_2", spawn_coroutines, &producer_index_2); test(id2 != -1); int rc; rc = machine_wait(id1); test(rc == 0); rc = machine_wait(id2); test(rc == 0); } void machinarium_test_tsan_simple_race_example(void) { machinarium_init(); int id; id = machine_create( "test_wait_list_notify_after_compare_wait_coroutines", test_simple_race_example, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_vrb.c000066400000000000000000000260411526064202200230200ustar00rootroot00000000000000#include #include #include size_t pagesz = 0; static void test_create_destroy(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(1024); test(vrb != NULL); test(mm_virtual_rbuf_size(vrb) == 0); test(mm_virtual_rbuf_free_size(vrb) == pagesz); test(mm_virtual_rbuf_capacity(vrb) == pagesz); mm_virtual_rbuf_free(vrb); } static void test_create_zero_size(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(0); test(vrb != NULL); test(mm_virtual_rbuf_size(vrb) == 0); test(mm_virtual_rbuf_free_size(vrb) == pagesz); test(mm_virtual_rbuf_capacity(vrb) == pagesz); mm_virtual_rbuf_free(vrb); } static void test_simple_write_read(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(0); test(vrb != NULL); test(mm_virtual_rbuf_capacity(vrb) == pagesz); const char *msg = "AAAAAAAABBBBBBBB"; size_t msg_len = strlen(msg); char buff[128]; /* lets make write-read cycle go out of bound of 0..capacity */ size_t n = (mm_virtual_rbuf_capacity(vrb) / msg_len) * 10; for (size_t i = 0; i < n; ++i) { test(mm_virtual_rbuf_write(vrb, msg, msg_len) == msg_len); test(mm_virtual_rbuf_size(vrb) == msg_len); test(mm_virtual_rbuf_free_size(vrb) == mm_virtual_rbuf_capacity(vrb) - msg_len); memset(buff, 0, msg_len); test(mm_virtual_rbuf_read(vrb, buff, msg_len) == msg_len); test(memcmp(buff, msg, msg_len) == 0); } test(mm_virtual_rbuf_free_size(vrb) == mm_virtual_rbuf_capacity(vrb)); test(mm_virtual_rbuf_size(vrb) == 0); mm_virtual_rbuf_free(vrb); } static void test_writes_read(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(4096); const char *msg1 = "First"; const char *msg2 = "Second"; const char *msg3 = "Third"; mm_virtual_rbuf_write(vrb, msg1, strlen(msg1)); mm_virtual_rbuf_write(vrb, msg2, strlen(msg2)); mm_virtual_rbuf_write(vrb, msg3, strlen(msg3)); size_t total = strlen(msg1) + strlen(msg2) + strlen(msg3); test(mm_virtual_rbuf_size(vrb) == total); char buf[100] = { 0 }; size_t nread = mm_virtual_rbuf_read(vrb, buf, total); test(nread == total); test(strncmp(buf, "FirstSecondThird", total) == 0); mm_virtual_rbuf_free(vrb); } static void test_write_read_partial(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(4096); const char *msg = "0123456789"; mm_virtual_rbuf_write(vrb, msg, 10); char buf[5] = { 0 }; size_t nread = mm_virtual_rbuf_read(vrb, buf, 5); test(nread == 5); test(strncmp(buf, "01234", 5) == 0); test(mm_virtual_rbuf_size(vrb) == 5); nread = mm_virtual_rbuf_read(vrb, buf, 5); test(nread == 5); test(strncmp(buf, "56789", 5) == 0); test(mm_virtual_rbuf_size(vrb) == 0); mm_virtual_rbuf_free(vrb); } static void test_write_overflow(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(pagesz); test(mm_virtual_rbuf_capacity(vrb) == pagesz); size_t data_size = pagesz * 2; char *big_data = malloc(data_size); memset(big_data, 'A', data_size); size_t written = mm_virtual_rbuf_write(vrb, big_data, data_size); test(written <= mm_virtual_rbuf_capacity(vrb)); test(written == mm_virtual_rbuf_capacity(vrb)); test(mm_virtual_rbuf_free_size(vrb) == 0); size_t written2 = mm_virtual_rbuf_write(vrb, "X", 1); test(written2 == 0); free(big_data); mm_virtual_rbuf_free(vrb); } static void test_read_empty(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(4096); char buf[10]; size_t nread = mm_virtual_rbuf_read(vrb, buf, 10); test(nread == 0); mm_virtual_rbuf_free(vrb); } static void test_wrap_around_write(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(pagesz); size_t data_size = pagesz - 4; char *data = malloc(data_size); memset(data, 'A', data_size); mm_virtual_rbuf_write(vrb, data, data_size); size_t nhalf = data_size / 2; mm_virtual_rbuf_read(vrb, data, nhalf); test(mm_virtual_rbuf_size(vrb) == nhalf); test(mm_virtual_rbuf_free_size(vrb) == (pagesz + nhalf - data_size)); const char *wrap_msg = "WRAP_AROUND_TEST"; size_t wrap_len = strlen(wrap_msg); size_t written = mm_virtual_rbuf_write(vrb, wrap_msg, wrap_len); test(written == wrap_len); mm_virtual_rbuf_read(vrb, data, nhalf); char buf[100] = { 0 }; size_t nread = mm_virtual_rbuf_read(vrb, buf, wrap_len); test(nread == wrap_len); test(strcmp(buf, wrap_msg) == 0); free(data); mm_virtual_rbuf_free(vrb); } static void test_drain(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(4096); const char *msg = "Test drain functionality"; size_t msg_len = strlen(msg); mm_virtual_rbuf_write(vrb, msg, msg_len); size_t drained = mm_virtual_rbuf_drain(vrb, msg_len / 2); test(drained == msg_len / 2); test(mm_virtual_rbuf_size(vrb) == msg_len - msg_len / 2); drained = mm_virtual_rbuf_drain(vrb, 1000); test(drained == msg_len - msg_len / 2); test(mm_virtual_rbuf_size(vrb) == 0); mm_virtual_rbuf_free(vrb); } static void test_drain_empty(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(4096); size_t drained = mm_virtual_rbuf_drain(vrb, 100); test(drained == 0); mm_virtual_rbuf_free(vrb); } static void test_write_begin_commit(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(4096); struct iovec vec = mm_virtual_rbuf_write_begin(vrb); test(vec.iov_base != NULL); test(vec.iov_len == mm_virtual_rbuf_capacity(vrb)); const char *msg = "Direct write test"; size_t msg_len = strlen(msg); memcpy(vec.iov_base, msg, msg_len); mm_virtual_rbuf_write_commit(vrb, msg_len); test(mm_virtual_rbuf_size(vrb) == msg_len); char buf[100] = { 0 }; mm_virtual_rbuf_read(vrb, buf, msg_len); test(strcmp(buf, msg) == 0); mm_virtual_rbuf_free(vrb); } static void test_read_begin_commit(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(pagesz); const char *msg = "Direct read test"; size_t msg_len = strlen(msg); mm_virtual_rbuf_write(vrb, msg, msg_len); struct iovec vec = mm_virtual_rbuf_read_begin(vrb); test(vec.iov_base != NULL); test(vec.iov_len == msg_len); test(memcmp(vec.iov_base, msg, msg_len) == 0); mm_virtual_rbuf_read_commit(vrb, msg_len); test(mm_virtual_rbuf_size(vrb) == 0); mm_virtual_rbuf_free(vrb); } static void test_begin_commit_across_boundary(void) { mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(pagesz); size_t filler_size = pagesz - 6; char *filler = malloc(filler_size); memset(filler, 'F', filler_size); mm_virtual_rbuf_write(vrb, filler, filler_size); mm_virtual_rbuf_drain(vrb, filler_size); struct iovec vec = mm_virtual_rbuf_write_begin(vrb); const char *msg = "BOUNDARY_TEST_MSG"; size_t msg_len = strlen(msg); memcpy(vec.iov_base, msg, msg_len); mm_virtual_rbuf_write_commit(vrb, msg_len); vec = mm_virtual_rbuf_read_begin(vrb); test(memcmp(vec.iov_base, msg, msg_len) == 0); mm_virtual_rbuf_read_commit(vrb, msg_len); free(filler); mm_virtual_rbuf_free(vrb); } void machinarium_test_vrb(void) { machinarium_init(); pagesz = getpagesize(); test_create_destroy(); test_create_zero_size(); test_simple_write_read(); test_writes_read(); test_write_read_partial(); test_write_overflow(); test_read_empty(); test_wrap_around_write(); test_drain(); test_drain_empty(); test_write_begin_commit(); test_read_begin_commit(); test_begin_commit_across_boundary(); machinarium_free(); } typedef struct { char *buffer; size_t size; } plain_memory_t; static plain_memory_t *plain_memory_create(size_t size) { plain_memory_t *pm = malloc(sizeof(plain_memory_t)); if (!pm) { return NULL; } pm->size = size; pm->buffer = malloc(size); if (!pm->buffer) { free(pm); return NULL; } return pm; } static void plain_memory_free(plain_memory_t *pm) { if (pm) { free(pm->buffer); free(pm); } } static size_t plain_memory_write(plain_memory_t *pm, const void *data, size_t len, size_t offset) { if (offset + len > pm->size) { len = pm->size - offset; } memcpy(pm->buffer + offset, data, len); return len; } #define BUFFER_SIZE (pagesz) #define ITERATIONS 1000000 static void benchmark_sequential_writes_1KB(void) { printf("\n=== VRB Benchmark: Sequential writes (1KB blocks) ===\n"); const size_t block_size = 1024; char *data = malloc(block_size); memset(data, 'A', block_size); benchmark_timer_t timer; mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(BUFFER_SIZE); timer_start(&timer); for (int i = 0; i < ITERATIONS; i++) { if (mm_virtual_rbuf_free_size(vrb) < block_size) { mm_virtual_rbuf_drain(vrb, BUFFER_SIZE); } mm_virtual_rbuf_write(vrb, data, block_size); } double vrb_time = timer_end(&timer); mm_virtual_rbuf_free(vrb); plain_memory_t *pm = plain_memory_create(BUFFER_SIZE); size_t offset = 0; timer_start(&timer); for (int i = 0; i < ITERATIONS; i++) { if (offset >= (BUFFER_SIZE - block_size)) { offset = 0; } plain_memory_write(pm, data, block_size, offset); offset += block_size; } double plain_time = timer_end(&timer); plain_memory_free(pm); double vrb_throughput = (ITERATIONS * block_size) / vrb_time / 1e6; double plain_throughput = (ITERATIONS * block_size) / plain_time / 1e6; printf("Virtual Ring Buffer: %.6f sec (%.2f MB/s)\n", vrb_time, vrb_throughput); printf("Plain Memory: %.6f sec (%.2f MB/s)\n", plain_time, plain_throughput); printf("Overhead: %.2f%% %s\n", ((vrb_time - plain_time) / plain_time) * 100, vrb_time > plain_time ? "(VRB slower)" : "(VRB faster)"); free(data); } static void benchmark_small_blocks(size_t block_size) { printf("\n=== VRB Benchmark: Small blocks (%lu bytes) ===\n", block_size); char data[1024]; memset(data, 'B', block_size); benchmark_timer_t timer; mm_virtual_rbuf_t *vrb = mm_virtual_rbuf_create(BUFFER_SIZE); timer_start(&timer); for (int i = 0; i < ITERATIONS; i++) { if (mm_virtual_rbuf_free_size(vrb) < block_size) { mm_virtual_rbuf_drain(vrb, BUFFER_SIZE); } if (mm_virtual_rbuf_write(vrb, data, block_size) != block_size) { abort(); } } double vrb_time = timer_end(&timer); mm_virtual_rbuf_free(vrb); plain_memory_t *pm = plain_memory_create(BUFFER_SIZE); size_t offset = 0; timer_start(&timer); for (int i = 0; i < ITERATIONS; i++) { if (offset >= (BUFFER_SIZE - block_size)) { offset = 0; } if (plain_memory_write(pm, data, block_size, offset) != block_size) { abort(); } offset += block_size; } double plain_time = timer_end(&timer); plain_memory_free(pm); double vrb_throughput = (ITERATIONS * block_size) / vrb_time / 1e6; double plain_throughput = (ITERATIONS * block_size) / plain_time / 1e6; printf("Virtual Ring Buffer: %.6f sec (%.2f MB/s)\n", vrb_time, vrb_throughput); printf("Plain Memory: %.6f sec (%.2f MB/s)\n", plain_time, plain_throughput); printf("Overhead: %.2f%% %s\n", ((vrb_time - plain_time) / plain_time) * 100, vrb_time > plain_time ? "(VRB slower)" : "(VRB faster)"); } void machinarium_vrb_benchmark(void) { machinarium_init(); pagesz = getpagesize(); benchmark_small_blocks(64); benchmark_small_blocks(128); benchmark_small_blocks(256); benchmark_small_blocks(512); benchmark_small_blocks(65); benchmark_small_blocks(129); benchmark_small_blocks(257); benchmark_small_blocks(513); benchmark_sequential_writes_1KB(); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_flag_simple.c000066400000000000000000000100131526064202200255250ustar00rootroot00000000000000#include #include #include atomic_uint_fast64_t flag; typedef struct { machine_wait_flag_t *flag; machine_wait_group_t *group; /* only for consumer */ uint32_t estimated_wait_time_lower_bound; uint32_t estimated_wait_time_upper_bound; } doner_arg_t; static inline void consumer(void *arg) { doner_arg_t *darg = arg; uint32_t start_time = machine_time_ms(); test(machine_wait_flag_wait(darg->flag, UINT32_MAX) == 0); uint32_t wait_time = machine_time_ms() - start_time; test(wait_time >= darg->estimated_wait_time_lower_bound); test(darg->estimated_wait_time_upper_bound >= wait_time); test(atomic_load(&flag) == 1); machine_wait_group_done(darg->group); } static inline void producer(void *arg) { doner_arg_t *darg = arg; atomic_store(&flag, 1); machine_wait_flag_set(darg->flag); machine_wait_group_done(darg->group); } static inline void test_wait_flag_simple_coroutines(void *arg) { (void)arg; machine_wait_flag_t *flag = machine_wait_flag_create(); machine_wait_group_t *group = machine_wait_group_create(); /* wait before set */ doner_arg_t arg1 = { .flag = flag, .group = group, .estimated_wait_time_lower_bound = 100, .estimated_wait_time_upper_bound = 105 }; machine_wait_group_add(group); int id1 = machine_coroutine_create(consumer, &arg1); test(id1 != -1); machine_sleep(100); doner_arg_t arg2 = { .flag = flag, .group = group }; machine_wait_group_add(group); int id2 = machine_coroutine_create(producer, &arg2); test(id2 != -1); machine_sleep(100); /* wait after set */ doner_arg_t arg3 = { .flag = flag, .group = group, .estimated_wait_time_lower_bound = 0, .estimated_wait_time_upper_bound = 5 }; machine_wait_group_add(group); int id3 = machine_coroutine_create(consumer, &arg3); test(id3 != -1); machine_sleep(100); /* second set */ doner_arg_t arg4 = { .flag = flag, .group = group }; machine_wait_group_add(group); int id4 = machine_coroutine_create(producer, &arg4); test(id4 != -1); machine_wait_group_wait(group, 1000); machine_wait_flag_destroy(flag); } static inline void test_wait_flag_simple_threads(void *arg) { (void)arg; machine_wait_flag_t *flag = machine_wait_flag_create(); machine_wait_group_t *group = machine_wait_group_create(); /* wait before set */ doner_arg_t arg1 = { .flag = flag, .group = group, .estimated_wait_time_lower_bound = 100, .estimated_wait_time_upper_bound = 105 }; machine_wait_group_add(group); int id1 = machine_create("test_wait_flag_consumer_1", consumer, &arg1); test(id1 != -1); machine_sleep(100); doner_arg_t arg2 = { .flag = flag, .group = group }; machine_wait_group_add(group); int id2 = machine_create("test_wait_flag_producer_1", producer, &arg2); test(id2 != -1); machine_sleep(100); /* wait after set */ doner_arg_t arg3 = { .flag = flag, .group = group, .estimated_wait_time_lower_bound = 0, .estimated_wait_time_upper_bound = 5 }; machine_wait_group_add(group); int id3 = machine_create("test_wait_flag_consumer_2", consumer, &arg3); test(id3 != -1); machine_sleep(100); /* second set */ doner_arg_t arg4 = { .flag = flag, .group = group }; machine_wait_group_add(group); int id4 = machine_create("test_wait_flag_producer_2", producer, &arg4); test(id4 != -1); machine_wait_group_wait(group, 1000); test(machine_wait(id1) != -1); test(machine_wait(id2) != -1); test(machine_wait(id3) != -1); test(machine_wait(id4) != -1); machine_wait_flag_destroy(flag); } void machinarium_test_wait_flag_simple(void) { machinarium_init(); atomic_init(&flag, 0); int id; id = machine_create("machinarium_test_wait_flag_simple_coroutines", test_wait_flag_simple_coroutines, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); atomic_store(&flag, 0); id = machine_create("machinarium_test_wait_flag_simple_threads", test_wait_flag_simple_threads, NULL); test(id != -1); rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_flag_timeout.c000066400000000000000000000011651526064202200257320ustar00rootroot00000000000000#include #include static inline void test_wait_flag_timeout(void *arg) { (void)arg; machine_wait_flag_t *flag = machine_wait_flag_create(); /* nobody sets the flag */ int rc = machine_wait_flag_wait(flag, 10); test(rc == -1); test(machine_errno() == ETIMEDOUT); machine_wait_flag_destroy(flag); } void machinarium_test_wait_flag_timeout(void) { machinarium_init(); int id; id = machine_create("machinarium_test_wait_flag_timeout", test_wait_flag_timeout, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_group_lifetime.c000066400000000000000000000102111526064202200262550ustar00rootroot00000000000000#include #include #include /* There is a wait group that is created and managed by the main thread. There are a few threads that wait for this wait group, and there is also a separate thread that destroys it. To synchronize the wait() calls with the destroy() call, a second wait group (waiters) is used. Every thread that waits for the first wait group also calls done() on the second one. The destroyer first waits for the second wait group (importantly, without timeout), and only then destroys the first one. Note that not all done() calls on the first wait group are completed when destroy() is called. */ typedef struct { machine_wait_group_t *group; machine_wait_group_t *waiters; } destroyer_arg_t; static inline void destroyer(void *arg) { destroyer_arg_t *darg = arg; machine_wait_group_wait(darg->waiters, UINT32_MAX); machine_wait_group_destroy(darg->group); } typedef struct { machine_wait_group_t *group; machine_wait_group_t *waiters; uint32_t timeout; uint32_t sleep_before_wait; } waiter_arg_t; static inline void waiter(void *arg) { waiter_arg_t *warg = arg; machine_wait_group_wait(warg->group, 50); machine_wait_group_done(warg->waiters); } typedef struct { machine_wait_group_t *group; uint32_t sleep_before_done; } doner_arg_t; static inline void doner(void *arg) { doner_arg_t *darg = arg; machine_sleep(darg->sleep_before_done); machine_wait_group_done(darg->group); } static inline void test_wait_group_lifetime(void *arg) { (void)arg; machine_wait_group_t *group = machine_wait_group_create(); machine_wait_group_t *waiters = machine_wait_group_create(); /* start doners */ doner_arg_t doner_arg1 = { .group = group, .sleep_before_done = 0 }; doner_arg_t doner_arg2 = { .group = group, .sleep_before_done = 0 }; doner_arg_t doner_arg3 = { .group = group, .sleep_before_done = 100 }; doner_arg_t doner_arg4 = { .group = group, .sleep_before_done = 100 }; machine_wait_group_add(group); int doner_id1 = machine_coroutine_create_named(doner, &doner_arg1, "doner_1"); test(doner_id1 != -1); machine_wait_group_add(group); int doner_id2 = machine_create("doner_2", doner, &doner_arg2); test(doner_id2 != -1); machine_wait_group_add(group); int doner_id3 = machine_coroutine_create_named(doner, &doner_arg3, "doner_3"); test(doner_id3 != -1); machine_wait_group_add(group); int doner_id4 = machine_create("doner_4", doner, &doner_arg4); test(doner_id4 != -1); /* start waiters */ waiter_arg_t waiter_arg1 = { .group = group, .waiters = waiters }; waiter_arg_t waiter_arg2 = { .group = group, .waiters = waiters }; waiter_arg_t waiter_arg3 = { .group = group, .waiters = waiters }; waiter_arg_t waiter_arg4 = { .group = group, .waiters = waiters }; machine_wait_group_add(waiters); int waiter_id1 = machine_coroutine_create_named(waiter, &waiter_arg1, "waiter_1"); test(waiter_id1 != -1); machine_wait_group_add(waiters); int waiter_id2 = machine_create("waiter_2", waiter, &waiter_arg2); test(waiter_id2 != -1); machine_wait_group_add(waiters); int waiter_id3 = machine_coroutine_create_named(waiter, &waiter_arg3, "waiter_3"); test(waiter_id3 != -1); machine_wait_group_add(waiters); int waiter_id4 = machine_create("waiter_4", waiter, &waiter_arg4); test(waiter_id4 != -1); /* start destroyer */ destroyer_arg_t destroyer_arg = { .group = group, .waiters = waiters }; int destroyer_id = machine_create("destroyer", destroyer, &destroyer_arg); test(destroyer_id != -1); /* free resources */ /* sleep is used here for simplicity, but is is wrong */ machine_sleep(150); machine_wait(destroyer_id); machine_join(waiter_id1); machine_wait(waiter_id2); machine_join(waiter_id3); machine_wait(waiter_id4); machine_join(doner_id1); machine_wait(doner_id2); machine_join(doner_id3); machine_wait(doner_id4); machine_wait_group_destroy(waiters); } void machinarium_test_wait_group_lifetime(void) { machinarium_init(); int id; id = machine_create("machinarium_test_wait_group_simple_coroutines", test_wait_group_lifetime, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_group_simple.c000066400000000000000000000053061526064202200257610ustar00rootroot00000000000000#include #include #include atomic_uint_fast64_t done_count; typedef struct { machine_wait_group_t *group; int num; } doner_arg_t; static inline void test_doner(void *arg) { doner_arg_t *darg = arg; machine_wait_group_t *group = darg->group; machine_sleep(darg->num * 50); atomic_fetch_add(&done_count, 1ULL); machine_wait_group_done(group); } static inline void test_wait_group_simple_coroutines(void *arg) { (void)arg; machine_wait_group_t *group = machine_wait_group_create(); doner_arg_t arg1 = { .group = group, .num = 1 }; machine_wait_group_add(group); int id1 = machine_coroutine_create(test_doner, &arg1); test(id1 != -1); doner_arg_t arg2 = { .group = group, .num = 2 }; machine_wait_group_add(group); int id2 = machine_coroutine_create(test_doner, &arg2); test(id2 != -1); doner_arg_t arg3 = { .group = group, .num = 3 }; machine_wait_group_add(group); int id3 = machine_coroutine_create(test_doner, &arg3); test(id3 != -1); test(machine_wait_group_count(group) == 3); test(machine_wait_group_wait(group, 300) == 0); test(atomic_load(&done_count) == 3); test(machine_wait_group_count(group) == 0); machine_wait_group_destroy(group); } static inline void test_wait_group_simple_threads(void *arg) { (void)arg; machine_wait_group_t *group = machine_wait_group_create(); doner_arg_t arg1 = { .group = group, .num = 1 }; machine_wait_group_add(group); int id1 = machine_create("done_thread1", test_doner, &arg1); test(id1 != -1); doner_arg_t arg2 = { .group = group, .num = 2 }; machine_wait_group_add(group); int id2 = machine_create("done_thread2", test_doner, &arg2); test(id2 != -1); doner_arg_t arg3 = { .group = group, .num = 3 }; machine_wait_group_add(group); int id3 = machine_create("done_thread1", test_doner, &arg3); test(id3 != -1); test(machine_wait_group_count(group) == 3); test(machine_wait_group_wait(group, 300) == 0); test(atomic_load(&done_count) == 3); test(machine_wait_group_count(group) == 0); int rc; rc = machine_wait(id1); test(rc != -1); rc = machine_wait(id2); test(rc != -1); rc = machine_wait(id3); test(rc != -1); machine_wait_group_destroy(group); } void machinarium_test_wait_group_simple(void) { machinarium_init(); atomic_init(&done_count, 0); int id; id = machine_create("machinarium_test_wait_group_simple_coroutines", test_wait_group_simple_coroutines, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); atomic_store(&done_count, 0); id = machine_create("machinarium_test_wait_group_simple_threads", test_wait_group_simple_threads, NULL); test(id != -1); rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_group_timeout.c000066400000000000000000000017751526064202200261640ustar00rootroot00000000000000#include #include static inline void test_example_coroutine(void *arg) { (void)arg; /* do something and forget to call done() */ machine_sleep(0); /* machine_wait_group_t *wl = arg; */ /* machine_wait_group_done(wl); */ } static inline void test_wait_group_timeout(void *arg) { (void)arg; machine_wait_group_t *group = machine_wait_group_create(); machine_wait_group_add(group); int id; id = machine_create("machinarium_test_wait_group_timeout", test_example_coroutine, NULL); test(id != -1); int rc = machine_wait_group_wait(group, 10); test(rc == -1); test(machine_errno() == ETIMEDOUT); rc = machine_wait(id); test(rc != -1); machine_wait_group_destroy(group); } void machinarium_test_wait_group_timeout(void) { machinarium_init(); int id; id = machine_create("machinarium_test_wait_group_timeout", test_wait_group_timeout, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_compare_wait_timeout.c000066400000000000000000000024151526064202200305250ustar00rootroot00000000000000#include #include #include #include typedef struct { mm_wait_list_t *wl; atomic_uint_fast64_t *wl_word; } test_arg_t; static inline void consumer(void *arg) { test_arg_t *t_arg = arg; mm_wait_list_t *wl = t_arg->wl; uint64_t start, end, total_time; int rc; start = machine_time_ms(); rc = mm_wait_list_compare_wait(wl, NULL, 0, 100); end = machine_time_ms(); test(rc == -1); test(machine_errno() == ETIMEDOUT); total_time = end - start; test(total_time >= 100); } static inline void test_compare_wait_timeout(void *arg) { (void)arg; atomic_uint_fast64_t atomic; atomic_store(&atomic, 0); mm_wait_list_t *wl = mm_wait_list_create(&atomic); test_arg_t t_arg; t_arg.wl = wl; t_arg.wl_word = &atomic; int consumer_id; consumer_id = machine_coroutine_create(consumer, &t_arg); test(consumer_id != -1); int rc; rc = machine_join(consumer_id); test(rc == 0); mm_wait_list_free(wl); } void machinarium_test_wait_list_compare_wait_timeout(void) { machinarium_init(); int id; id = machine_create("test_wait_list_compare_wait_timeout", test_compare_wait_timeout, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_compare_wait_wrong_value.c000066400000000000000000000037141526064202200313720ustar00rootroot00000000000000#include #include #include #include typedef struct { mm_wait_list_t *wl; atomic_uint_fast64_t *wl_word; } test_arg_t; static inline void consumer(void *arg) { test_arg_t *t_arg = arg; mm_wait_list_t *wl = t_arg->wl; uint64_t start, end, total_time; int rc; machine_sleep(100); start = machine_time_ms(); rc = mm_wait_list_compare_wait(wl, NULL, 0, 1000); end = machine_time_ms(); test(rc == -1); test(machine_errno() == EAGAIN); total_time = end - start; test(total_time < 5); } static inline void test_compare_wait_wrong_value_coroutines(void *arg) { (void)arg; atomic_uint_fast64_t atomic; atomic_store(&atomic, 1); mm_wait_list_t *wl = mm_wait_list_create(&atomic); test_arg_t t_arg; t_arg.wl = wl; t_arg.wl_word = &atomic; int consumer_id; consumer_id = machine_coroutine_create(consumer, &t_arg); test(consumer_id != -1); int rc; rc = machine_join(consumer_id); test(rc == 0); mm_wait_list_free(wl); } static inline void test_compare_wait_wrong_value_threads(void *arg) { (void)arg; atomic_uint_fast64_t atomic; atomic_store(&atomic, 1); mm_wait_list_t *wl = mm_wait_list_create(&atomic); test_arg_t t_arg; t_arg.wl = wl; t_arg.wl_word = &atomic; int consumer_id; consumer_id = machine_create("consumer_thread", consumer, &t_arg); test(consumer_id != -1); int rc; rc = machine_wait(consumer_id); test(rc == 0); mm_wait_list_free(wl); } void machinarium_test_wait_list_compare_wait_wrong_value(void) { machinarium_init(); int id; id = machine_create( "test_wait_list_compare_wait_wrong_value_coroutines", test_compare_wait_wrong_value_coroutines, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); id = machine_create("test_wait_list_compare_wait_wrong_value_threads", test_compare_wait_wrong_value_threads, NULL); test(id != -1); rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_notify_after_compare_wait.c000066400000000000000000000053661526064202200315400ustar00rootroot00000000000000#include #include #include #include typedef struct { mm_wait_list_t *wl; atomic_uint_fast64_t *wl_word; } test_arg_t; static void *opaque = (void *)0xdeadbeefcafebabe; static void opaque_check(void *op, void *arg) { (void)op; (void)arg; test(op == opaque); test(arg == opaque); } static inline void producer(void *arg) { test_arg_t *t_arg = arg; mm_wait_list_t *wl = t_arg->wl; atomic_uint_fast64_t *wl_word = t_arg->wl_word; machine_sleep(500); atomic_store(wl_word, 1); test(mm_wait_list_notify_cb(wl, opaque_check, opaque) == opaque); } static inline void consumer(void *arg) { test_arg_t *t_arg = arg; mm_wait_list_t *wl = t_arg->wl; atomic_uint_fast64_t *wl_word = t_arg->wl_word; uint64_t start, end, total_time; int rc; start = machine_time_ms(); rc = mm_wait_list_compare_wait(wl, opaque, 0, 1000); end = machine_time_ms(); test(rc == 0); total_time = end - start; test(total_time > 400 && total_time < 1000); test(atomic_load(wl_word) == 1) } static inline void test_notify_after_compare_wait_coroutines(void *arg) { (void)arg; atomic_uint_fast64_t atomic; atomic_store(&atomic, 0); mm_wait_list_t *wl = mm_wait_list_create(&atomic); test_arg_t t_arg; t_arg.wl = wl; t_arg.wl_word = &atomic; int consumer_id; consumer_id = machine_coroutine_create(consumer, &t_arg); test(consumer_id != -1); int producer_id; producer_id = machine_coroutine_create(producer, &t_arg); test(producer_id != -1); int rc; rc = machine_join(producer_id); test(rc == 0); rc = machine_join(consumer_id); test(rc == 0); mm_wait_list_free(wl); } static inline void test_notify_after_compare_wait_threads(void *arg) { (void)arg; atomic_uint_fast64_t atomic; atomic_store(&atomic, 0); mm_wait_list_t *wl = mm_wait_list_create(&atomic); test_arg_t t_arg; t_arg.wl = wl; t_arg.wl_word = &atomic; int consumer_id; consumer_id = machine_create("consumer_thread", consumer, &t_arg); test(consumer_id != -1); int producer_id; producer_id = machine_create("producer_thread", producer, &t_arg); test(producer_id != -1); int rc; rc = machine_wait(producer_id); test(rc == 0); rc = machine_wait(consumer_id); test(rc == 0); mm_wait_list_free(wl); } void machinarium_test_wait_list_notify_after_compare_wait(void) { machinarium_init(); int id; id = machine_create( "test_wait_list_notify_after_compare_wait_coroutines", test_notify_after_compare_wait_coroutines, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); id = machine_create("test_wait_list_notify_after_compare_wait_threads", test_notify_after_compare_wait_threads, NULL); test(id != -1); rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_notify_after_wait.c000066400000000000000000000024421526064202200300220ustar00rootroot00000000000000#include #include #include static inline void producer_coroutine(void *arg) { mm_wait_list_t *wl = arg; machine_sleep(500); mm_wait_list_notify(wl); } static inline void consumer_coroutine(void *arg) { mm_wait_list_t *wl = arg; uint64_t start, end, total_time; int rc; start = machine_time_ms(); rc = mm_wait_list_wait(wl, NULL, 1000); end = machine_time_ms(); test(rc == 0); total_time = end - start; test(total_time > 400 && total_time < 1000); } static inline void test_notify_after_wait(void *arg) { (void)arg; mm_wait_list_t *wl = mm_wait_list_create(NULL); int consumer_id; consumer_id = machine_coroutine_create(consumer_coroutine, wl); test(consumer_id != -1); int producer_id; producer_id = machine_coroutine_create(producer_coroutine, wl); test(producer_id != -1); machine_sleep(0); int rc; rc = machine_join(producer_id); test(rc == 0); rc = machine_join(consumer_id); test(rc == 0); mm_wait_list_free(wl); } void machinarium_test_wait_list_notify_after_wait(void) { machinarium_init(); int id; id = machine_create("test_wait_list_notify_after_wait", test_notify_after_wait, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_notify_all.c000066400000000000000000000040731526064202200264470ustar00rootroot00000000000000#include #include #include #define NUM_THREADS 10 typedef struct { mm_wait_list_t *wait_list; int num; } waiter_arg_t; static inline void test_waiter_coroutine(void *arg) { waiter_arg_t *warg = arg; mm_wait_list_t *wait_list = warg->wait_list; int rc = mm_wait_list_wait(wait_list, NULL, 10000); test(rc == 0); /* * sleep here to parent coroutine's machine_join * catch this coro before destroying */ machine_sleep(warg->num * 50); } static inline void test_waiter_thread(void *arg) { (void)arg; waiter_arg_t *warg = arg; waiter_arg_t arg1 = { .wait_list = warg->wait_list, .num = warg->num }; int waiter1 = machine_coroutine_create(test_waiter_coroutine, &arg1); test(waiter1 != -1); waiter_arg_t arg2 = { .wait_list = warg->wait_list, .num = warg->num + 1 }; int waiter2 = machine_coroutine_create(test_waiter_coroutine, &arg2); test(waiter2 != -1); int rc; rc = machine_join(waiter1); test(rc == 0); rc = machine_join(waiter2); test(rc == 0); } static inline void test_notify_all(void *arg) { (void)arg; mm_wait_list_t *wl = mm_wait_list_create(NULL); int waiters[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; ++i) { waiter_arg_t *arg = malloc(sizeof(waiter_arg_t)); arg->wait_list = wl; arg->num = 1 + 2 * i; char buf[256]; snprintf(buf, sizeof(buf), "%s%d", "test_waiter_thread_", i + 1); int waiter = machine_create(buf, test_waiter_thread, arg); test(waiter != -1); waiters[i] = waiter; } machine_sleep(1000); mm_wait_list_notify_all(wl); int rc; for (int i = 0; i < NUM_THREADS; ++i) { rc = machine_wait(waiters[i]); test(rc == 0); } /* check that notify_all doesn't break when the wait list is empty */ mm_wait_list_notify_all(wl); mm_wait_list_free(wl); } void machinarium_test_wait_list_notify_all(void) { machinarium_init(); int id; id = machine_create("machinarium_test_wait_list_notify_all", test_notify_all, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_one_producer_multiple_consumers.c000066400000000000000000000035641526064202200330100ustar00rootroot00000000000000#include #include #include static inline void producer_coroutine(void *arg) { mm_wait_list_t *wl = arg; uint64_t start, current; start = machine_time_ms(); current = start; while ((current - start) < 4000) { mm_wait_list_notify(wl); machine_sleep(300); current = machine_time_ms(); } } typedef struct { mm_wait_list_t *wl; int count; int64_t id; } consumer_arg_t; static inline void consumer_coroutine(void *arg) { consumer_arg_t *ca = arg; mm_wait_list_t *wl = ca->wl; int *count = &ca->count; uint64_t start, current; int rc; start = machine_time_ms(); current = start; while ((current - start) < 3000) { rc = mm_wait_list_wait(wl, NULL, 1000); test(rc == 0); ++(*count); current = machine_time_ms(); } } static inline void test_multiple_consumers(void *arg) { (void)arg; mm_wait_list_t *wl = mm_wait_list_create(NULL); int producer_id; producer_id = machine_coroutine_create(producer_coroutine, wl); test(producer_id != -1); consumer_arg_t a1, a2, a3; int c1, c2, c3; a1.count = a2.count = a3.count = 0; a1.wl = a2.wl = a3.wl = wl; c1 = machine_coroutine_create(consumer_coroutine, &a1); test(c1 != -1); a1.id = c1; c2 = machine_coroutine_create(consumer_coroutine, &a2); test(c2 != -1); a2.id = c2; c3 = machine_coroutine_create(consumer_coroutine, &a3); test(c3 != -1); a3.id = c3; machine_sleep(0); int rc; rc = machine_join(producer_id); test(rc == 0); test(a1.count >= 3); test(a2.count >= 3); test(a3.count >= 3); mm_wait_list_free(wl); } void machinarium_test_wait_list_one_producer_multiple_consumers(void) { machinarium_init(); int id; id = machine_create("test_wait_list_one_producer_multiple_consumers", test_multiple_consumers, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_one_producer_multiple_consumers_threads.c000066400000000000000000000040121526064202200345070ustar00rootroot00000000000000#include #include #include static inline void producer_coroutine(void *arg) { mm_wait_list_t *wl = arg; uint64_t start, current; start = machine_time_ms(); current = start; while ((current - start) < 4000) { mm_wait_list_notify(wl); machine_sleep(300); current = machine_time_ms(); } } typedef struct { mm_wait_list_t *wl; int count; int64_t id; } consumer_arg_t; static inline void consumer_thread(void *arg) { consumer_arg_t *ca = arg; mm_wait_list_t *wl = ca->wl; int *count = &ca->count; uint64_t start, current; int rc; start = machine_time_ms(); current = start; while ((current - start) < 3000) { rc = mm_wait_list_wait(wl, NULL, 1000); test(rc == 0); ++(*count); current = machine_time_ms(); } } static inline void test_multiple_consumers_threads(void *arg) { (void)arg; mm_wait_list_t *wl = mm_wait_list_create(NULL); int producer_id; producer_id = machine_coroutine_create(producer_coroutine, wl); test(producer_id != -1); consumer_arg_t a1, a2, a3; int c1, c2, c3; a1.count = a2.count = a3.count = 0; a1.wl = a2.wl = a3.wl = wl; c1 = machine_create("consumer1", consumer_thread, &a1); test(c1 != -1); a1.id = c1; c2 = machine_create("consumer2", consumer_thread, &a2); test(c2 != -1); a2.id = c2; c3 = machine_create("consumer3", consumer_thread, &a3); test(c3 != -1); a3.id = c3; machine_sleep(0); int rc; rc = machine_join(producer_id); test(rc == 0); rc = machine_wait(c1); test(rc == 0); rc = machine_wait(c2); test(rc == 0); rc = machine_wait(c3); test(rc == 0); test(a1.count >= 3); test(a2.count >= 3); test(a3.count >= 3); mm_wait_list_free(wl); } void machinarium_test_wait_list_one_producer_multiple_consumers_threads(void) { machinarium_init(); int id; id = machine_create( "test_wait_list_one_producer_multiple_consumers_threads", test_multiple_consumers_threads, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/machinarium/test_wait_list_without_notify.c000066400000000000000000000022551526064202200274020ustar00rootroot00000000000000#include #include #include static inline void test_wait_without_notify_coroutine(void *arg) { (void)arg; mm_wait_list_t *wait_list = mm_wait_list_create(NULL); uint64_t start, end; start = machine_time_ms(); int rc; rc = mm_wait_list_wait(wait_list, NULL, 1000); end = machine_time_ms(); test(rc == -1); test(machine_errno() == ETIMEDOUT); test(end - start >= 1000); /* notify without waiters should be ignored */ mm_wait_list_notify(wait_list); rc = mm_wait_list_wait(wait_list, NULL, 1000); test(rc == -1); test(machine_errno() == ETIMEDOUT); mm_wait_list_free(wait_list); } static inline void test_wait_without_notify(void *arg) { (void)arg; int id; id = machine_coroutine_create(test_wait_without_notify_coroutine, NULL); test(id != -1); machine_sleep(0); int rc; rc = machine_join(id); test(rc == 0); } void machinarium_test_wait_list_without_notify(void) { machinarium_init(); int id; id = machine_create("test_wait_list_wait_without_notify", test_wait_without_notify, NULL); test(id != -1); int rc; rc = machine_wait(id); test(rc != -1); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/odyssey-test.conf000066400000000000000000000025141526064202200220510ustar00rootroot00000000000000daemonize no unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers 2 resolvers 2 readahead 8192 cache_coroutine 0 coroutine_stack_size 8 nodelay yes keepalive 7200 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" } } storage "local" { type "local" } database "console" { user default { authentication "none" pool "session" storage "local" } } odyssey-1.6.0-rc1/sources/tests/odyssey/000077500000000000000000000000001526064202200202235ustar00rootroot00000000000000odyssey-1.6.0-rc1/sources/tests/odyssey/test_address.c000066400000000000000000000166431526064202200230650ustar00rootroot00000000000000#include #include #include typedef struct { char *host; char *zone; int port; od_address_type_t type; } parse_test_arg_t; static inline parse_test_arg_t address(char *h, char *z, int p) { parse_test_arg_t r = { .host = h, .zone = z, .port = p, .type = OD_ADDRESS_TYPE_TCP }; return r; } static inline parse_test_arg_t address_unix(char *h, char *z, int p) { parse_test_arg_t r = { .host = h, .zone = z, .port = p, .type = OD_ADDRESS_TYPE_UNIX }; return r; } static inline void do_test(const char *line, int expected_rc, size_t expected_count, ...) { od_address_t *addresses = NULL; size_t count = 0; int rc = od_parse_addresses(line, &addresses, &count); test(rc == expected_rc); test(count == expected_count); va_list args; va_start(args, expected_count); for (size_t i = 0; i < count; ++i) { parse_test_arg_t expected_endpoint = va_arg(args, parse_test_arg_t); test(strcmp(expected_endpoint.host, addresses[i].host) == 0); test(strcmp(expected_endpoint.zone, addresses[i].availability_zone) == 0); test(expected_endpoint.port == addresses[i].port); test(expected_endpoint.type == addresses[i].type); free(addresses[i].host); } free(addresses); va_end(args); } void odyssey_test_address_parse(void) { do_test("localhost", OK_RESPONSE, 1, address("localhost", "", 0)); do_test("[localhost]", OK_RESPONSE, 1, address("localhost", "", 0)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net", OK_RESPONSE, 1, address("sas-sokkge3ejever6ae.mdb.yandex.net", "", 0)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:1337", OK_RESPONSE, 1, address("sas-sokkge3ejever6ae.mdb.yandex.net", "", 1337)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:1337:sas", OK_RESPONSE, 1, address("sas-sokkge3ejever6ae.mdb.yandex.net", "sas", 1337)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:sas", OK_RESPONSE, 1, address("sas-sokkge3ejever6ae.mdb.yandex.net", "sas", 0)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:sas:klg", NOT_OK_RESPONSE, 0); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:sas, sas-sokkge3ejever6ae.mdb.yandex.net:sas:klg", NOT_OK_RESPONSE, 0); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:sas:1337", OK_RESPONSE, 1, address("sas-sokkge3ejever6ae.mdb.yandex.net", "sas", 1337)); do_test("[::1]:sas:1337", OK_RESPONSE, 1, address("::1", "sas", 1337)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net,localhost,klg-kd19rbngltphuob9.mdb.yandex.net", OK_RESPONSE, 3, address("sas-sokkge3ejever6ae.mdb.yandex.net", "", 0), address("localhost", "", 0), address("klg-kd19rbngltphuob9.mdb.yandex.net", "", 0)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:1337,localhost,klg-kd19rbngltphuob9.mdb.yandex.net:31337", OK_RESPONSE, 3, address("sas-sokkge3ejever6ae.mdb.yandex.net", "", 1337), address("localhost", "", 0), address("klg-kd19rbngltphuob9.mdb.yandex.net", "", 31337)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:1337,[localhost],klg-kd19rbngltphuob9.mdb.yandex.net:31337", OK_RESPONSE, 3, address("sas-sokkge3ejever6ae.mdb.yandex.net", "", 1337), address("localhost", "", 0), address("klg-kd19rbngltphuob9.mdb.yandex.net", "", 31337)); do_test("sas-sokkge3ejever6ae.mdb.yandex.net:1337:sas,localhost:5432:vla,klg-kd19rbngltphuob9.mdb.yandex.net:31337:klg", OK_RESPONSE, 3, address("sas-sokkge3ejever6ae.mdb.yandex.net", "sas", 1337), address("localhost", "vla", 5432), address("klg-kd19rbngltphuob9.mdb.yandex.net", "klg", 31337)); do_test("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:1337:sas,localhost:5432:vla,[2001:0db8:85a3:1234:5678:8a2e:1375:7334]:31337:klg", OK_RESPONSE, 3, address("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "sas", 1337), address("localhost", "vla", 5432), address("2001:0db8:85a3:1234:5678:8a2e:1375:7334", "klg", 31337)); do_test("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1337:sas,localhost:5432:vla,[2001:0db8:85a3:1234:5678:8a2e:1375:7334]:31337:klg", NOT_OK_RESPONSE, 0); do_test("tcp://sas-sokkge3ejever6ae.mdb.yandex.net:sas", OK_RESPONSE, 1, address("sas-sokkge3ejever6ae.mdb.yandex.net", "sas", 0)); do_test("tcp://localhost:31337:klg", OK_RESPONSE, 1, address("localhost", "klg", 31337)); do_test("tcp://127.9.12.34:31337:klg", OK_RESPONSE, 1, address("127.9.12.34", "klg", 31337)); do_test("tcp://[2001:0db8:85a3:1234:5678:8a2e:1375:7334]:31337:klg", OK_RESPONSE, 1, address("2001:0db8:85a3:1234:5678:8a2e:1375:7334", "klg", 31337)); do_test("unix:///var/lib/postgresql/.s.5432", OK_RESPONSE, 1, address_unix("/var/lib/postgresql/.s.5432", "", 0)); do_test("tcp://[2001:0db8:85a3:1234:5678:8a2e:1375:7334]:31337:klg,unix:///var/lib/postgresql/.s.5432", OK_RESPONSE, 2, address("2001:0db8:85a3:1234:5678:8a2e:1375:7334", "klg", 31337), address_unix("/var/lib/postgresql/.s.5432", "", 0)); do_test("", NOT_OK_RESPONSE, 0); do_test(",", NOT_OK_RESPONSE, 0); do_test(",,", NOT_OK_RESPONSE, 0); do_test("a,", OK_RESPONSE, 1, address("a", "", 0)); do_test(",a", OK_RESPONSE, 1, address("a", "", 0)); do_test("a,,b", OK_RESPONSE, 2, address("a", "", 0), address("b", "", 0)); do_test(",a,,b,", OK_RESPONSE, 2, address("a", "", 0), address("b", "", 0)); } static inline int sign(int a) { if (a > 0) { return 1; } if (a < 0) { return -1; } return 0; } static inline void do_cmp_test(parse_test_arg_t addr1, parse_test_arg_t addr2, int expected) { od_address_t a, b; od_address_init(&a); od_address_init(&b); a.type = addr1.type; strcpy(a.availability_zone, addr1.zone); a.host = addr1.host; a.port = addr1.port; b.type = addr2.type; strcpy(b.availability_zone, addr2.zone); b.host = addr2.host; b.port = addr2.port; int sut = od_address_cmp(&a, &b); test(sign(sut) == sign(expected)); /* no od_address_destroy here */ } void odyssey_test_address_cmp(void) { do_cmp_test(address("localhost", "kl", 1337), address_unix("localhost", "kl", 1337), OD_ADDRESS_TYPE_TCP - OD_ADDRESS_TYPE_UNIX); do_cmp_test(address_unix("localhost", "kl", 1337), address("localhost", "kl", 1337), OD_ADDRESS_TYPE_UNIX - OD_ADDRESS_TYPE_TCP); do_cmp_test(address("localhost", "kl", 1337), address("localhost", "kl", 1337), 0); do_cmp_test(address("localhost", "kl", 1338), address("localhost", "kl", 1337), 1338 - 1337); do_cmp_test(address("localhost", "kl", 1337), address("localhost", "kl", 1338), 1337 - 1338); do_cmp_test(address("localhost", "ko", 1337), address("localhost", "kl", 1337), strcmp("ko", "kl")); do_cmp_test(address("localhost", "kl", 1337), address("localhost", "ko", 1337), strcmp("kl", "ko")); do_cmp_test(address("127.0.0.1", "kl", 1337), address("127.0.0.8", "kl", 1337), strcmp("127.0.0.1", "127.0.0.8")); do_cmp_test(address("127.0.0.8", "kl", 1337), address("127.0.0.1", "kl", 1337), strcmp("127.0.0.8", "127.0.0.1")); do_cmp_test(address_unix("/var/lib/.s.5432", "", 0), address_unix("/var/lib/.s.5432", "", 0), 0); do_cmp_test(address_unix("/var/lib/.s.5432", "dsfs", 0), address_unix("/var/lib/.s.5432", "sdfgs", 0), 0); do_cmp_test(address_unix("/var/lib/.s.5432", "", 4131), address_unix("/var/lib/.s.5432", "", 3134), 0); do_cmp_test(address_unix("/var/lib/.s.54322", "", 0), address_unix("/var/lib/.s.54324", "", 0), strcmp("/var/lib/.s.54322", "/var/lib/.s.54324")); do_cmp_test(address_unix("/var/lib/.s.54324", "", 0), address_unix("/var/lib/.s.54322", "", 0), strcmp("/var/lib/.s.54324", "/var/lib/.s.54322")); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_affinity.c000066400000000000000000000341751526064202200232510ustar00rootroot00000000000000#include #include #include #include static void do_cpuset_parse_test(const char *str, int n, ...) { va_list args; va_start(args, n); od_affinity_cpuset_t set; int rc = od_affinity_cpuset_parse(str, &set, NULL, 0); if (n == 0) { test(rc < 0); } else { test(rc == 0); test(n == od_affinity_cpuset_count(&set)); for (int i = 0; i < n; ++i) { int num = va_arg(args, int); test(od_affinity_cpuset_get(&set, num) == 1); } } va_end(args); } static void cpuset_parse_tests(void) { do_cpuset_parse_test("", 0); do_cpuset_parse_test(",", 0); do_cpuset_parse_test(",,", 0); do_cpuset_parse_test(",0", 0); do_cpuset_parse_test("0,", 0); do_cpuset_parse_test("0,,1", 0); do_cpuset_parse_test("-", 0); do_cpuset_parse_test("0-", 0); do_cpuset_parse_test("-1", 0); do_cpuset_parse_test("1-", 0); do_cpuset_parse_test("1--2", 0); do_cpuset_parse_test("a", 0); do_cpuset_parse_test("abc", 0); do_cpuset_parse_test("a1", 0); do_cpuset_parse_test("1a", 0); do_cpuset_parse_test("1,a", 0); do_cpuset_parse_test("a,1", 0); do_cpuset_parse_test("1-2x", 0); do_cpuset_parse_test("1x-2", 0); do_cpuset_parse_test("2-1", 0); do_cpuset_parse_test("10-3", 0); do_cpuset_parse_test("1 2", 0); do_cpuset_parse_test("1 -2", 0); do_cpuset_parse_test("1- 2", 0); do_cpuset_parse_test("1 ,2", 0); do_cpuset_parse_test("1, 2", 0); do_cpuset_parse_test("0", 1, 0); do_cpuset_parse_test("1", 1, 1); do_cpuset_parse_test("7", 1, 7); do_cpuset_parse_test("0,1", 2, 0, 1); do_cpuset_parse_test("1,0", 2, 0, 1); do_cpuset_parse_test("0,1,2", 3, 0, 1, 2); do_cpuset_parse_test("2,4,6", 3, 2, 4, 6); do_cpuset_parse_test("0-0", 1, 0); do_cpuset_parse_test("0-1", 2, 0, 1); do_cpuset_parse_test("1-3", 3, 1, 2, 3); do_cpuset_parse_test("3-5", 3, 3, 4, 5); do_cpuset_parse_test("0-2,4", 4, 0, 1, 2, 4); do_cpuset_parse_test("0,2-4", 4, 0, 2, 3, 4); do_cpuset_parse_test("0-2,4-6", 6, 0, 1, 2, 4, 5, 6); do_cpuset_parse_test("1,3-5,7", 5, 1, 3, 4, 5, 7); do_cpuset_parse_test("0,2,4-6,8", 6, 0, 2, 4, 5, 6, 8); do_cpuset_parse_test("0,0", 1, 0); do_cpuset_parse_test("1,1,1", 1, 1); do_cpuset_parse_test("0-2,1", 3, 0, 1, 2); do_cpuset_parse_test("1,0-2", 3, 0, 1, 2); do_cpuset_parse_test("0-2,1-3", 4, 0, 1, 2, 3); do_cpuset_parse_test("1,2,2,3,3,3", 3, 1, 2, 3); do_cpuset_parse_test("0", 1, 0); do_cpuset_parse_test("0-1", 2, 0, 1); { char buf[64]; od_snprintf(buf, sizeof(buf), "%d", OD_AFFINITY_MAX_CPUS - 1); do_cpuset_parse_test(buf, 1, OD_AFFINITY_MAX_CPUS - 1); } { char buf[64]; od_snprintf(buf, sizeof(buf), "%d-%d", OD_AFFINITY_MAX_CPUS - 2, OD_AFFINITY_MAX_CPUS - 1); do_cpuset_parse_test(buf, 2, OD_AFFINITY_MAX_CPUS - 2, OD_AFFINITY_MAX_CPUS - 1); } { char buf[64]; od_snprintf(buf, sizeof(buf), "%d", OD_AFFINITY_MAX_CPUS); do_cpuset_parse_test(buf, 0); } { char buf[64]; od_snprintf(buf, sizeof(buf), "0-%d", OD_AFFINITY_MAX_CPUS); do_cpuset_parse_test(buf, 0); } { char buf[64]; od_snprintf(buf, sizeof(buf), "%d-%d", OD_AFFINITY_MAX_CPUS - 1, OD_AFFINITY_MAX_CPUS); do_cpuset_parse_test(buf, 0); } do_cpuset_parse_test("-0", 0); do_cpuset_parse_test("-1", 0); do_cpuset_parse_test("0,-1", 0); do_cpuset_parse_test("-1,0", 0); do_cpuset_parse_test("-1--2", 0); do_cpuset_parse_test("0x", 0); do_cpuset_parse_test("1-2x", 0); do_cpuset_parse_test("1,2x", 0); do_cpuset_parse_test("1-2,3x", 0); do_cpuset_parse_test("1-2,", 0); do_cpuset_parse_test("5,3,1", 3, 1, 3, 5); do_cpuset_parse_test("5-7,1-3", 6, 1, 2, 3, 5, 6, 7); do_cpuset_parse_test("1,3-4,6,8-9", 6, 1, 3, 4, 6, 8, 9); } static void do_rule_parse_test(const char *str, od_affinity_role_t role, int index, int n, ...) { od_affinity_rule_t rule; od_affinity_rule_init(&rule); va_list args; va_start(args, n); int rc = od_affinity_rule_parse(str, &rule, NULL, 0); if (n == 0) { test(rc < 0); } else { test(rc == 0); test(rule.index == index); test(rule.role == role); test(n == od_affinity_cpuset_count(&rule.cpuset)); for (int i = 0; i < n; ++i) { int num = va_arg(args, int); test(od_affinity_cpuset_get(&rule.cpuset, num) == 1); } } va_end(args); } static void rule_parse_tests(void) { do_rule_parse_test("", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test(":", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:3-4handshake", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake:", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test(":0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test(":0-3", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("wrk:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("workers:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("hs:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("foo:0-3", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[0:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker0]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[0]]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[0:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake0]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[0]]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[-1]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[abc]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[1x]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker[x1]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[-1]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[abc]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[1x]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake[x1]:0", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:abc", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:1,", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:,1", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:3-1", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake:abc", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake:1,", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake:,1", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("handshake:3-1", OD_AFFINITY_ROLE_NONE, -1, 0); do_rule_parse_test("worker:0", OD_AFFINITY_ROLE_WORKER, -1, 1, 0); do_rule_parse_test("worker:1", OD_AFFINITY_ROLE_WORKER, -1, 1, 1); do_rule_parse_test("worker:0-3", OD_AFFINITY_ROLE_WORKER, -1, 4, 0, 1, 2, 3); do_rule_parse_test("worker:1,3,5", OD_AFFINITY_ROLE_WORKER, -1, 3, 1, 3, 5); do_rule_parse_test("worker:0-2,4", OD_AFFINITY_ROLE_WORKER, -1, 4, 0, 1, 2, 4); do_rule_parse_test("handshake:0", OD_AFFINITY_ROLE_HANDSHAKE, -1, 1, 0); do_rule_parse_test("handshake:2", OD_AFFINITY_ROLE_HANDSHAKE, -1, 1, 2); do_rule_parse_test("handshake:2-3", OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 2, 3); do_rule_parse_test("handshake:1,4,7", OD_AFFINITY_ROLE_HANDSHAKE, -1, 3, 1, 4, 7); do_rule_parse_test("handshake:0-1,3-4", OD_AFFINITY_ROLE_HANDSHAKE, -1, 4, 0, 1, 3, 4); do_rule_parse_test("worker[0]:0", OD_AFFINITY_ROLE_WORKER, 0, 1, 0); do_rule_parse_test("worker[1]:1", OD_AFFINITY_ROLE_WORKER, 1, 1, 1); do_rule_parse_test("worker[7]:2-3", OD_AFFINITY_ROLE_WORKER, 7, 2, 2, 3); do_rule_parse_test("worker[10]:0,2,4", OD_AFFINITY_ROLE_WORKER, 10, 3, 0, 2, 4); do_rule_parse_test("handshake[0]:0", OD_AFFINITY_ROLE_HANDSHAKE, 0, 1, 0); do_rule_parse_test("handshake[1]:3", OD_AFFINITY_ROLE_HANDSHAKE, 1, 1, 3); do_rule_parse_test("handshake[2]:4-5", OD_AFFINITY_ROLE_HANDSHAKE, 2, 2, 4, 5); do_rule_parse_test("handshake[9]:1,3,7", OD_AFFINITY_ROLE_HANDSHAKE, 9, 3, 1, 3, 7); do_rule_parse_test("worker:0,0", OD_AFFINITY_ROLE_WORKER, -1, 1, 0); do_rule_parse_test("worker:0-2,1", OD_AFFINITY_ROLE_WORKER, -1, 3, 0, 1, 2); do_rule_parse_test("handshake:2,2,2", OD_AFFINITY_ROLE_HANDSHAKE, -1, 1, 2); do_rule_parse_test("handshake:1-3,2-4", OD_AFFINITY_ROLE_HANDSHAKE, -1, 4, 1, 2, 3, 4); } static od_affinity_rule_t rule(od_affinity_role_t role, int index, int n, ...) { od_affinity_rule_t rule; od_affinity_rule_init(&rule); va_list args; va_start(args, n); rule.role = role; rule.index = index; for (int i = 0; i < n; ++i) { int num = va_arg(args, int); od_affinity_cpuset_add(&rule.cpuset, num); } va_end(args); return rule; } static void do_config_parse_test(const char *str, int rc, od_affinity_mode_t mode, size_t n, ...) { od_affinity_config_t config; int sut = od_affinity_config_parse(str, strlen(str), &config, NULL, 0); test(sut == rc); if (rc < 0) { return; } test(config.mode == mode); if (mode == OD_AFFINITY_MODE_AUTO || mode == OD_AFFINITY_MODE_OFF) { test(config.nrules == 0); } else { test(config.nrules == n); va_list args; va_start(args, n); for (size_t i = 0; i < n; ++i) { od_affinity_rule_t expected = va_arg(args, od_affinity_rule_t); test(memcmp(&config.rules[i], &expected, sizeof(od_affinity_rule_t)) == 0); } va_end(args); } } static void config_parse_tests(void) { do_config_parse_test("", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test(" ", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("\t", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test(" \t ", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("off", 0, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("no", 0, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("auto", 0, OD_AFFINITY_MODE_AUTO, 0); do_config_parse_test("yes", 0, OD_AFFINITY_MODE_AUTO, 0); do_config_parse_test("off worker:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("auto worker:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("yes worker:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("no worker:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker:0", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_WORKER, -1, 1, 0)); do_config_parse_test("worker:0-4,6", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_WORKER, -1, 6, 0, 1, 2, 3, 4, 6)); do_config_parse_test("handshake:1", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 1, 1)); do_config_parse_test("handshake:2-3", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 2, 3)); do_config_parse_test("worker[0]:0", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_WORKER, 0, 1, 0)); do_config_parse_test("worker[7]:2-3", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_WORKER, 7, 2, 2, 3)); do_config_parse_test("handshake[0]:4", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_HANDSHAKE, 0, 1, 4)); do_config_parse_test("handshake[2]:4-5", 0, OD_AFFINITY_MODE_RULES, 1, rule(OD_AFFINITY_ROLE_HANDSHAKE, 2, 2, 4, 5)); do_config_parse_test("worker:0-3 handshake:4-5", 0, OD_AFFINITY_MODE_RULES, 2, rule(OD_AFFINITY_ROLE_WORKER, -1, 4, 0, 1, 2, 3), rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 4, 5)); do_config_parse_test("worker[0]:0 worker[1]:1 handshake:2-3", 0, OD_AFFINITY_MODE_RULES, 3, rule(OD_AFFINITY_ROLE_WORKER, 0, 1, 0), rule(OD_AFFINITY_ROLE_WORKER, 1, 1, 1), rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 2, 3)); do_config_parse_test("worker:4-7 handshake[0]:2 handshake[1]:3", 0, OD_AFFINITY_MODE_RULES, 3, rule(OD_AFFINITY_ROLE_WORKER, -1, 4, 4, 5, 6, 7), rule(OD_AFFINITY_ROLE_HANDSHAKE, 0, 1, 2), rule(OD_AFFINITY_ROLE_HANDSHAKE, 1, 1, 3)); do_config_parse_test("worker:0-1\t\thandshake:2-3", 0, OD_AFFINITY_MODE_RULES, 2, rule(OD_AFFINITY_ROLE_WORKER, -1, 2, 0, 1), rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 2, 3)); do_config_parse_test("worker:0-1 handshake:2-3", 0, OD_AFFINITY_MODE_RULES, 2, rule(OD_AFFINITY_ROLE_WORKER, -1, 2, 0, 1), rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 2, 3)); do_config_parse_test("worker:0,0,1 handshake:2,2", 0, OD_AFFINITY_MODE_RULES, 2, rule(OD_AFFINITY_ROLE_WORKER, -1, 2, 0, 1), rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 1, 2)); do_config_parse_test("worker:0 worker:1", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake:0 handshake:1", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker[0]:0 worker[0]:1", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake[1]:2 handshake[1]:3", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test( "worker:0-3 worker[0]:4 handshake:5-6 handshake[1]:7", 0, OD_AFFINITY_MODE_RULES, 4, rule(OD_AFFINITY_ROLE_WORKER, -1, 4, 0, 1, 2, 3), rule(OD_AFFINITY_ROLE_WORKER, 0, 1, 4), rule(OD_AFFINITY_ROLE_HANDSHAKE, -1, 2, 5, 6), rule(OD_AFFINITY_ROLE_HANDSHAKE, 1, 1, 7)); do_config_parse_test("worker", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker:", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake:", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker[]:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake[]:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker[abc]:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake[-1]:0", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker:abc", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake:3-1", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("worker:1,", -1, OD_AFFINITY_MODE_OFF, 0); do_config_parse_test("handshake:,1", -1, OD_AFFINITY_MODE_OFF, 0); } void odyssey_test_affinity(void) { cpuset_parse_tests(); rule_parse_tests(); config_parse_tests(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_attribute.c000066400000000000000000000041461526064202200234360ustar00rootroot00000000000000#include #include #include void test_read_attribute_buf_sanity(void) { char str[] = "a=qwerty,b=,c=other"; char *data = malloc(strlen(str)); size_t ptr_size = strlen(str); memcpy(data, str, ptr_size); char *ptr = data; char *value; size_t value_size; test(read_attribute_buf(&ptr, &ptr_size, 'a', &value, &value_size) == 0); test(memcmp(value, "qwerty", value_size) == 0); test(read_attribute_buf(&ptr, &ptr_size, 'b', &value, &value_size) == 0); test(memcmp(value, "", value_size) == 0); test(read_attribute_buf(&ptr, &ptr_size, 'c', &value, &value_size) == 0); test(memcmp(value, "other", value_size) == 0); free(data); } void test_read_any_attribute_buf_sanity(void) { char str[] = "a=qwerty,b=,c=other"; size_t data_size = strlen(str); char *data = malloc(data_size); memcpy(data, str, data_size); { char *ptr = data; size_t ptr_size = data_size; char *value; size_t value_size; char attribute; test(read_any_attribute_buf(&ptr, &ptr_size, &attribute, &value, &value_size) == 0); test(memcmp(value, "qwerty", value_size) == 0); test(attribute == 'a'); test(read_any_attribute_buf(&ptr, &ptr_size, &attribute, &value, &value_size) == 0); test(memcmp(value, "", value_size) == 0); test(attribute == 'b'); test(read_any_attribute_buf(&ptr, &ptr_size, &attribute, &value, &value_size) == 0); test(memcmp(value, "other", value_size) == 0); test(attribute == 'c'); } { char *ptr = data; size_t ptr_size = data_size; char *value; size_t value_size; test(read_any_attribute_buf(&ptr, &ptr_size, NULL, &value, &value_size) == 0); test(memcmp(value, "qwerty", value_size) == 0); test(read_any_attribute_buf(&ptr, &ptr_size, NULL, &value, &value_size) == 0); test(memcmp(value, "", value_size) == 0); test(read_any_attribute_buf(&ptr, &ptr_size, NULL, &value, &value_size) == 0); test(memcmp(value, "other", value_size) == 0); } free(data); } void odyssey_test_attribute(void) { test_read_attribute_buf_sanity(); test_read_any_attribute_buf_sanity(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_console_parser.c000066400000000000000000000202161526064202200244450ustar00rootroot00000000000000#include #include #include #include #include #define PRINT_BUF_SIZE 1024 #define ARENA_SIZE 8192 static uint8_t s_arena_buf[ARENA_SIZE]; static od_linear_alloc_t s_arena; static void on_error(const char *msg, void *userdata) { strcpy((char *)userdata, msg); } static const char *parse_ok(const char *input) { static char out[PRINT_BUF_SIZE]; static char err[PRINT_BUF_SIZE]; memset(err, 0, sizeof(err)); memset(out, 0, sizeof(out)); od_linear_alloc_reset(&s_arena, 0); od_console_node_t *node = od_console_parse(input, strlen(input), &s_arena, on_error, err); if (node == NULL || strlen(err) != 0) { fprintf(stderr, "parse_ok failed: input=[%s] err=[%s] node=%p\n", input, err, (void *)node); abort(); } int n = od_console_node_print(node, out, sizeof(out)); test(n > 0 && (size_t)n < sizeof(out)); return out; } static void parse_fail(const char *input) { static char err[PRINT_BUF_SIZE]; memset(err, 0, sizeof(err)); od_linear_alloc_reset(&s_arena, 0); od_console_node_t *node = od_console_parse(input, strlen(input), &s_arena, on_error, err); if (node != NULL) { fprintf(stderr, "parse_fail: expected failure but got node for [%s]\n", input); abort(); } test(node == NULL); } /* * SHOW tests */ static void test_show_stats(void) { test(strcmp(parse_ok("SHOW STATS"), "(show stats)") == 0); } static void test_show_help(void) { test(strcmp(parse_ok("SHOW HELP"), "(show help)") == 0); } static void test_show_pools(void) { test(strcmp(parse_ok("SHOW POOLS"), "(show pools)") == 0); } static void test_show_pools_extended(void) { test(strcmp(parse_ok("SHOW POOLS_EXTENDED"), "(show pools_extended)") == 0); } static void test_show_databases(void) { test(strcmp(parse_ok("SHOW DATABASES"), "(show databases)") == 0); } static void test_show_servers(void) { test(strcmp(parse_ok("SHOW SERVERS"), "(show servers)") == 0); } static void test_show_server_prep_stmts(void) { test(strcmp(parse_ok("SHOW SERVER_PREP_STMTS"), "(show server_prep_stmts)") == 0); } static void test_show_global_prepared_stmts(void) { test(strcmp(parse_ok("SHOW GLOBAL_PREPARED_STATEMENTS"), "(show global_prepared_statements)") == 0); } static void test_show_clients(void) { test(strcmp(parse_ok("SHOW CLIENTS"), "(show clients)") == 0); } static void test_show_lists(void) { test(strcmp(parse_ok("SHOW LISTS"), "(show lists)") == 0); } static void test_show_instance(void) { test(strcmp(parse_ok("SHOW INSTANCE"), "(show instance)") == 0); } static void test_show_errors(void) { test(strcmp(parse_ok("SHOW ERRORS"), "(show errors)") == 0); } static void test_show_errors_per_route(void) { test(strcmp(parse_ok("SHOW ERRORS_PER_ROUTE"), "(show errors_per_route)") == 0); } static void test_show_version(void) { test(strcmp(parse_ok("SHOW VERSION"), "(show version)") == 0); test(strcmp(parse_ok("SHOW VERSION_EXTENDED"), "(show version_extended)") == 0); } static void test_show_listen(void) { test(strcmp(parse_ok("SHOW LISTEN"), "(show listen)") == 0); } static void test_show_storages(void) { test(strcmp(parse_ok("SHOW STORAGES"), "(show storages)") == 0); } static void test_show_fds(void) { test(strcmp(parse_ok("SHOW FDS"), "(show fds)") == 0); } static void test_show_is_paused(void) { test(strcmp(parse_ok("SHOW IS_PAUSED"), "(show is_paused)") == 0); } static void test_show_host_utilization(void) { test(strcmp(parse_ok("SHOW HOST_UTILIZATION"), "(show host_utilization)") == 0); } static void test_show_rules(void) { test(strcmp(parse_ok("SHOW RULES"), "(show rules)") == 0); } static void test_show_config(void) { test(strcmp(parse_ok("SHOW CONFIG"), "(show config)") == 0); } static void test_show_case_insensitive(void) { test(strcmp(parse_ok("show stats"), "(show stats)") == 0); test(strcmp(parse_ok("Show Stats"), "(show stats)") == 0); test(strcmp(parse_ok("SHOW STATS;"), "(show stats)") == 0); } static void test_show_whitespace(void) { test(strcmp(parse_ok(" SHOW STATS "), "(show stats)") == 0); } static void test_show_with_block_comment(void) { test(strcmp(parse_ok("SHOW /* comment */ STATS"), "(show stats)") == 0); } /* * KILL_CLIENT tests */ static void test_kill_client_basic(void) { const char *res = parse_ok("KILL_CLIENT c3f1a2b4e890c"); test(strcmp(res, "(kill-client c3f1a2b4e890c)") == 0); } static void test_kill_client_case_insensitive_cmd(void) { test(strcmp(parse_ok("kill_client c3f1a2b4e890c"), "(kill-client c3f1a2b4e890c)") == 0); } static void test_kill_client_with_semicolon(void) { test(strcmp(parse_ok("KILL_CLIENT c3f1a2b4e890c;"), "(kill-client c3f1a2b4e890c)") == 0); } static void test_kill_client_bad_id_length(void) { parse_fail("KILL_CLIENT c3f1"); parse_fail("KILL_CLIENT c3f1a2b4e890cd"); parse_fail("KILL_CLIENT"); } /* * Admin command tests */ static void test_reload(void) { test(strcmp(parse_ok("RELOAD"), "(reload)") == 0); test(strcmp(parse_ok("reload"), "(reload)") == 0); test(strcmp(parse_ok("RELOAD;"), "(reload)") == 0); } static void test_pause(void) { test(strcmp(parse_ok("PAUSE"), "(pause)") == 0); test(strcmp(parse_ok("pause"), "(pause)") == 0); test(strcmp(parse_ok("PAUSE;"), "(pause)") == 0); } static void test_resume(void) { test(strcmp(parse_ok("RESUME"), "(resume)") == 0); test(strcmp(parse_ok("resume"), "(resume)") == 0); test(strcmp(parse_ok("RESUME;"), "(resume)") == 0); } static void test_gc(void) { test(strcmp(parse_ok("GC"), "(gc)") == 0); test(strcmp(parse_ok("gc"), "(gc)") == 0); test(strcmp(parse_ok("GC;"), "(gc)") == 0); } static void test_set_equals(void) { test(strcmp(parse_ok("SET key = value"), "(set key=value)") == 0); } static void test_set_to(void) { test(strcmp(parse_ok("SET key TO value"), "(set key=value)") == 0); } static void test_set_string_value(void) { test(strcmp(parse_ok("SET key = 'myvalue'"), "(set key=myvalue)") == 0); } static void test_set_default(void) { test(strcmp(parse_ok("SET key = DEFAULT"), "(set key=default)") == 0); test(strcmp(parse_ok("SET key TO DEFAULT"), "(set key=default)") == 0); } static void test_set_integer(void) { test(strcmp(parse_ok("SET key = 42"), "(set key=42)") == 0); } static void test_set_with_semicolon(void) { test(strcmp(parse_ok("SET key = value;"), "(set key=value)") == 0); } static void test_drop_servers(void) { test(strcmp(parse_ok("DROP SERVERS"), "(drop servers)") == 0); test(strcmp(parse_ok("drop servers"), "(drop servers)") == 0); test(strcmp(parse_ok("DROP SERVERS;"), "(drop servers)") == 0); } static void test_empty_input(void) { od_linear_alloc_reset(&s_arena, 0); od_console_node_t *node = od_console_parse("", 0, &s_arena, NULL, NULL); test(node == NULL); } static void test_parse_errors(void) { parse_fail("SELECT 1"); parse_fail("SHOW"); parse_fail("KILL_CLIENT"); parse_fail("RELOAD extra"); parse_fail("PAUSE extra"); parse_fail("RESUME extra"); parse_fail("GC extra"); parse_fail("SET"); parse_fail("SET key"); parse_fail("DROP"); parse_fail("DROP UNKNOWN"); parse_fail("COMMIT"); parse_fail("BEGIN"); parse_fail("DEALLOCATE ALL"); parse_fail("DISCARD ALL"); } void odyssey_test_console_parser(void) { od_linear_alloc_init(&s_arena, s_arena_buf, sizeof(s_arena_buf)); test_show_stats(); test_show_help(); test_show_pools(); test_show_pools_extended(); test_show_databases(); test_show_servers(); test_show_server_prep_stmts(); test_show_global_prepared_stmts(); test_show_clients(); test_show_lists(); test_show_instance(); test_show_errors(); test_show_errors_per_route(); test_show_version(); test_show_listen(); test_show_storages(); test_show_fds(); test_show_is_paused(); test_show_host_utilization(); test_show_rules(); test_show_config(); test_show_case_insensitive(); test_show_whitespace(); test_show_with_block_comment(); test_kill_client_basic(); test_kill_client_case_insensitive_cmd(); test_kill_client_with_semicolon(); test_kill_client_bad_id_length(); test_reload(); test_pause(); test_resume(); test_gc(); test_set_equals(); test_set_to(); test_set_string_value(); test_set_default(); test_set_integer(); test_set_with_semicolon(); test_drop_servers(); test_empty_input(); test_parse_errors(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_duration_parse.c000066400000000000000000000127761526064202200244620ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include /* * Helpers — call the pure string API directly, no od_parser_t needed. * parse_ms / parse_s assert that parsing succeeds and return microseconds. * try_parse_ms returns false when parsing fails (used for error-path tests). */ static uint64_t parse_ms(const char *input) { uint64_t us = 0; test(od_duration_parse_ms(input, strlen(input), &us) > 0); return us; } static uint64_t parse_s(const char *input) { uint64_t us = 0; test(od_duration_parse_s(input, strlen(input), &us) > 0); return us; } static bool try_parse_ms(const char *input, uint64_t *out_us) { return od_duration_parse_ms(input, strlen(input), out_us) > 0; } static void test_suffix_ns(void) { test(parse_ms("2000000ns") == 2000ULL); } static void test_suffix_us(void) { test(parse_ms("5000us") == 5000ULL); } static void test_suffix_ms(void) { test(parse_ms("500ms") == 500000ULL); } static void test_suffix_s(void) { test(parse_ms("30s") == 30000000ULL); } static void test_suffix_m(void) { test(parse_ms("1m") == 60000000ULL); } static void test_suffix_h(void) { test(parse_ms("2h") == 7200000000ULL); } static void test_suffix_d(void) { test(parse_ms("1d") == 86400000000ULL); } static void test_compound_1h30m(void) { test(parse_ms("1h30m") == 5400000000ULL); } static void test_compound_1m30s(void) { test(parse_ms("1m30s") == 90000000ULL); } static void test_compound_2d3h(void) { test(parse_ms("2d3h") == 183600000000ULL); } static void test_compound_1d2h3m4s(void) { uint64_t expected = 1ULL * 24 * 60 * 60 * 1000000 + 2ULL * 60 * 60 * 1000000 + 3ULL * 60 * 1000000 + 4ULL * 1000000; test(parse_ms("1d2h3m4s") == expected); } static void test_compound_1m500ms(void) { test(parse_ms("1m500ms") == 60500000ULL); } /* * Backward compatibility: bare number with no suffix. * * These tests document that existing config files continue to work exactly * as they did before duration parsing was added. Each test names the * affected field and shows the stored-value math so a reviewer can verify * the conversion without reading config_reader.c. */ static void test_compat_ms_field_bare_number(void) { /* * Fields like pool_timeout store milliseconds as int. * Old config: pool_timeout 5000 * parse_ms("5000") -> 5 000 000 us; stored: (int)(us / 1000) = 5000 ms */ uint64_t us = parse_ms("5000"); test(us == 5000000ULL); test((int)(us / 1000ULL) == 5000); } static void test_compat_s_field_bare_number_u64(void) { /* * Fields like pool_client_idle_timeout store microseconds as uint64_t. * Old config: pool_client_idle_timeout 30 * parse_s("30") -> 30 000 000 us; stored directly as uint64_t */ uint64_t us = parse_s("30"); test(us == 30000000ULL); } static void test_compat_s_field_bare_number_int(void) { /* * Fields like catchup_timeout store seconds as int. * Old config: catchup_timeout 10 * parse_s("10") -> 10 000 000 us; stored: (int)(us / 1 000 000) = 10 s */ uint64_t us = parse_s("10"); test(us == 10000000ULL); test((int)(us / 1000000ULL) == 10); } static void test_no_suffix_zero(void) { test(parse_ms("0") == 0ULL); test(parse_s("0") == 0ULL); } /* Explicit suffix must win over the field's default unit. */ static void test_suffix_overrides_default(void) { test(parse_s("30s") == 30000000ULL); test(parse_s("2m") == 120000000ULL); test(parse_s("1h30m") == 5400000000ULL); } static void test_case_insensitive(void) { test(parse_ms("1S") == 1000000ULL); test(parse_ms("1M") == 60000000ULL); test(parse_ms("1H") == 3600000000ULL); test(parse_ms("1D") == 86400000000ULL); test(parse_ms("500MS") == 500000ULL); } static void test_zero_with_suffix(void) { test(parse_ms("0s") == 0ULL); test(parse_ms("0m") == 0ULL); test(parse_ms("0d") == 0ULL); } static void test_one_week(void) { test(parse_ms("7d") == 604800000000ULL); } /* * Nanoseconds are truncated to microseconds via integer division. * Values below 1000 ns round to 0 us. */ static void test_ns_truncation(void) { test(parse_ms("1ns") == 0ULL); test(parse_ms("999ns") == 0ULL); test(parse_ms("1000ns") == 1ULL); } /* * od_duration_parse skips leading whitespace and includes it in the * returned consumed count. */ static void test_leading_whitespace(void) { test(parse_ms(" 30s") == 30000000ULL); test(parse_ms(" 1h30m") == 5400000000ULL); } /* * The parser must reject any input that does not start with a digit. */ static void test_invalid_returns_false(void) { uint64_t us; /* alphabetic string — not a number at all */ test(!try_parse_ms("notanumber", &us)); /* suffix with no leading number */ test(!try_parse_ms("ms", &us)); test(!try_parse_ms("s", &us)); test(!try_parse_ms("h", &us)); /* empty string */ test(!try_parse_ms("", &us)); /* whitespace only */ test(!try_parse_ms(" ", &us)); /* sign not followed by digits */ test(!try_parse_ms("+5s", &us)); } void odyssey_test_duration_parse(void) { test_suffix_ns(); test_suffix_us(); test_suffix_ms(); test_suffix_s(); test_suffix_m(); test_suffix_h(); test_suffix_d(); test_compound_1h30m(); test_compound_1m30s(); test_compound_2d3h(); test_compound_1d2h3m4s(); test_compound_1m500ms(); test_compat_ms_field_bare_number(); test_compat_s_field_bare_number_u64(); test_compat_s_field_bare_number_int(); test_no_suffix_zero(); test_suffix_overrides_default(); test_case_insensitive(); test_zero_with_suffix(); test_one_week(); test_ns_truncation(); test_leading_whitespace(); test_invalid_returns_false(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_hba_parse.c000066400000000000000000000023501526064202200233520ustar00rootroot00000000000000#include #include #include #include #include #include #include void test_od_hba_reader_prefix(sa_family_t net, char *prefix, char *value) { od_hba_rule_t *hba = NULL; char buffer[INET6_ADDRSTRLEN]; hba = od_hba_rule_create(); hba->address_range.addr.ss_family = net; test(od_address_range_read_prefix(&hba->address_range, prefix) == 0); if (net == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *)&hba->address_range.mask; inet_ntop(net, &addr->sin_addr, buffer, sizeof(buffer)); } else { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&hba->address_range.mask; inet_ntop(net, &addr->sin6_addr, buffer, sizeof(buffer)); } test(memcmp(value, buffer, strlen(buffer)) == 0); od_hba_rule_free(hba); } void odyssey_test_hba(void) { test_od_hba_reader_prefix(AF_INET, "31", "255.255.255.254"); test_od_hba_reader_prefix(AF_INET, "24", "255.255.255.0"); test_od_hba_reader_prefix(AF_INET, "12", "255.240.0.0"); test_od_hba_reader_prefix(AF_INET, "7", "254.0.0.0"); test_od_hba_reader_prefix(AF_INET6, "10", "ffc0::"); test_od_hba_reader_prefix(AF_INET6, "120", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00"); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_linear_alloc.c000066400000000000000000000163641526064202200240640ustar00rootroot00000000000000#include #include #include static void test_init(void) { _Alignas(max_align_t) uint8_t buf[64]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); test(al.buf == buf); test(al.size == sizeof(buf)); test(al.used == 0); test(al.track_sizes == 0); od_linear_alloc_destroy(&al); } static void test_alloc_basic(void) { _Alignas(max_align_t) uint8_t buf[64]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); /* plain mode has no per-block header */ void *p = od_linear_alloc_alloc(&al, 16, 0); test(p != NULL); test(p == buf); test(al.used == 16); void *p2 = od_linear_alloc_alloc(&al, 16, 0); test(p2 != NULL); test(p2 != p); test(al.used == 32); od_linear_alloc_destroy(&al); } static void test_alloc_zeroed(void) { _Alignas(max_align_t) uint8_t buf[64]; memset(buf, 0xff, sizeof(buf)); od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); char *p = od_linear_alloc_alloc(&al, 32, OD_LINEAR_ALLOC_ZERO); test(p != NULL); for (size_t i = 0; i < 32; i++) { test(p[i] == 0); } od_linear_alloc_destroy(&al); } static void test_alloc_overflow(void) { _Alignas(max_align_t) uint8_t buf[32]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); void *p = od_linear_alloc_alloc(&al, 16, 0); test(p != NULL); void *p2 = od_linear_alloc_alloc(&al, 16, 0); test(p2 != NULL); test(al.used == 32); void *p3 = od_linear_alloc_alloc(&al, 1, 0); test(p3 == NULL); od_linear_alloc_destroy(&al); } static void test_alloc_exact_fit(void) { _Alignas(max_align_t) uint8_t buf[32]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); void *p = od_linear_alloc_alloc(&al, sizeof(buf), 0); test(p != NULL); test(p == buf); test(al.used == sizeof(buf)); void *p2 = od_linear_alloc_alloc(&al, 1, 0); test(p2 == NULL); od_linear_alloc_destroy(&al); } static void test_alloc_alignment(void) { _Alignas(max_align_t) uint8_t buf[256]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); void *p = od_linear_alloc_alloc(&al, 1, 0); test(p != NULL); test(((uintptr_t)p % alignof(max_align_t)) == 0); void *p2 = od_linear_alloc_alloc(&al, 7, 0); test(p2 != NULL); test(((uintptr_t)p2 % alignof(max_align_t)) == 0); void *p3 = od_linear_alloc_alloc(&al, 13, 0); test(p3 != NULL); test(((uintptr_t)p3 % alignof(max_align_t)) == 0); od_linear_alloc_destroy(&al); } static void test_mode_switch(void) { _Alignas(max_align_t) uint8_t buf[64]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); /* realloc mode pays an alignment header per block */ od_linear_alloc_reset(&al, OD_LINEAR_ALLOC_REALLOC); void *p = od_linear_alloc_alloc(&al, 8, 0); test(p != NULL); test(p == buf + alignof(max_align_t)); test(al.used == 2 * alignof(max_align_t)); /* plain mode after reset does not */ od_linear_alloc_reset(&al, 0); void *p2 = od_linear_alloc_alloc(&al, 8, 0); test(p2 != NULL); test(p2 == buf); test(al.used == alignof(max_align_t)); od_linear_alloc_destroy(&al); } static void test_reset(void) { _Alignas(max_align_t) uint8_t buf[64]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); void *p1 = od_linear_alloc_alloc(&al, 32, 0); test(p1 != NULL); test(p1 == buf); test(al.used == 32); od_linear_alloc_reset(&al, 0); test(al.used == 0); void *p2 = od_linear_alloc_alloc(&al, sizeof(buf), 0); test(p2 != NULL); test(p2 == buf); void *p3 = od_linear_alloc_alloc(&al, 1, 0); test(p3 == NULL); od_linear_alloc_destroy(&al); } static void test_free_is_noop(void) { _Alignas(max_align_t) uint8_t buf[64]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); void *p = od_linear_alloc_alloc(&al, 16, 0); test(p != NULL); size_t used_before = al.used; od_linear_alloc_free(&al, p); test(al.used == used_before); od_linear_alloc_destroy(&al); } static void test_realloc_disabled(void) { _Alignas(max_align_t) uint8_t buf[64]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); void *p = od_linear_alloc_alloc(&al, 16, 0); test(p != NULL); /* realloc of an existing block requires OD_LINEAR_ALLOC_REALLOC */ void *p2 = od_linear_alloc_realloc(&al, p, 32); test(p2 == NULL); test(al.used == 16); /* realloc(NULL) is an alloc in any mode */ void *p3 = od_linear_alloc_realloc(&al, NULL, 16); test(p3 != NULL); test(al.used == 32); od_linear_alloc_destroy(&al); } static void test_realloc_mode(void) { _Alignas(max_align_t) uint8_t buf[256]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, OD_LINEAR_ALLOC_REALLOC); /* grow the last block in place */ char *p = od_linear_alloc_alloc(&al, 8, 0); test(p != NULL); memset(p, 'a', 8); test(al.used == 2 * alignof(max_align_t)); char *g = od_linear_alloc_realloc(&al, p, 32); test(g == p); test(al.used == alignof(max_align_t) + 32); memset(g + 8, 'b', 24); /* shrink the last block in place */ void *p2 = od_linear_alloc_alloc(&al, 8, 0); test(p2 != NULL); memset(p2, 'c', 8); test(al.used == 3 * alignof(max_align_t) + 32); char *s = od_linear_alloc_alloc(&al, 32, 0); test(s != NULL); char *s2 = od_linear_alloc_realloc(&al, s, 16); test(s2 == s); test(al.used == 4 * alignof(max_align_t) + 48); /* realloc to zero frees and returns NULL */ test(od_linear_alloc_realloc(&al, s2, 0) == NULL); test(al.used == 4 * alignof(max_align_t) + 48); /* non-last block: reallocated by copy, old block stays in arena */ char *g2 = od_linear_alloc_realloc(&al, p2, 64); test(g2 != NULL); test(g2 != p2); test(g2[0] == 'c' && g2[7] == 'c'); test(g[0] == 'a' && g[31] == 'b'); test(al.used == 5 * alignof(max_align_t) + 112); /* exhausted arena: realloc fails and does not modify the arena */ test(od_linear_alloc_realloc(&al, p, sizeof(buf)) == NULL); test(al.used == 5 * alignof(max_align_t) + 112); od_linear_alloc_destroy(&al); } static void test_realloc_mode_zeroed(void) { _Alignas(max_align_t) uint8_t buf[64]; memset(buf, 0xff, sizeof(buf)); od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, OD_LINEAR_ALLOC_REALLOC); char *p = od_linear_alloc_alloc(&al, 16, OD_LINEAR_ALLOC_ZERO); test(p != NULL); for (size_t i = 0; i < 16; i++) { test(p[i] == 0); } od_linear_alloc_destroy(&al); } static void test_many_allocs(void) { _Alignas(max_align_t) uint8_t buf[1024]; od_linear_alloc_t al; od_linear_alloc_init(&al, buf, sizeof(buf)); od_linear_alloc_reset(&al, 0); int n = 0; for (size_t s = 1; s <= 64; s++) { void *p = od_linear_alloc_alloc(&al, s, 0); if (p == NULL) { break; } n++; } test(n > 0); od_linear_alloc_reset(&al, 0); test(al.used == 0); od_linear_alloc_destroy(&al); } void odyssey_test_linear_alloc(void) { test_init(); test_alloc_basic(); test_alloc_zeroed(); test_alloc_overflow(); test_alloc_exact_fit(); test_alloc_alignment(); test_mode_switch(); test_reset(); test_free_is_noop(); test_realloc_disabled(); test_realloc_mode(); test_realloc_mode_zeroed(); test_many_allocs(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_pstmt.c000066400000000000000000000446761526064202200226160ustar00rootroot00000000000000#include #include #include #include #include #include #include #define ARENA_SIZE 8192 static uint8_t s_arena_buf[ARENA_SIZE]; static od_linear_alloc_t s_arena; static void test_refs(void) { od_global_pstmt_map_t *global = od_global_pstmts_map_create(1); test(global != NULL); od_pstmt_desc_t desc; desc.data = "data1"; desc.len = sizeof("data1"); od_pstmt_t *ps = od_pstmt_create_or_get(global, desc, &s_arena); test(ps != NULL); od_pstmt_unref(ps); test(od_global_pstmts_has_pstmt(global, desc) == 0); ps = od_pstmt_create_or_get(global, desc, &s_arena); test(ps != NULL); od_pstmt_t *p2 = ps; od_pstmt_ref(ps); od_pstmt_unref(ps); test(od_global_pstmts_has_pstmt(global, desc) == 1); od_pstmt_unref(p2); test(od_global_pstmts_has_pstmt(global, desc) == 0); od_global_pstmts_map_free(global); } static void test_pstmt_global(void) { od_global_pstmt_map_t *gm = od_global_pstmts_map_create(1); od_pstmt_desc_t desc; desc.data = "data"; desc.len = sizeof("data"); od_pstmt_t *pstmt = od_pstmt_create_or_get(gm, desc, &s_arena); test(pstmt != NULL); test(desc.len == pstmt->desc.len); test(memcmp(desc.data, pstmt->desc.data, pstmt->desc.len) == 0); test(&pstmt->desc != &desc); test(pstmt->desc.data != desc.data); od_pstmt_t *t = od_pstmt_create_or_get(gm, desc, &s_arena); test(t == pstmt); od_pstmt_unref(t); od_pstmt_t *pstmt2 = od_pstmt_create_or_get(gm, desc, &s_arena); test(pstmt2 == pstmt); test(pstmt2->desc.data == pstmt->desc.data); od_pstmt_desc_t desc_copy; desc_copy.data = od_strdup("data"); test(desc.data != NULL); desc_copy.len = sizeof("data"); od_pstmt_t *pstmt3 = od_pstmt_create_or_get(gm, desc_copy, &s_arena); test(pstmt3 == pstmt2); test(pstmt3 == pstmt); od_pstmt_unref(pstmt); od_pstmt_unref(pstmt2); od_pstmt_unref(pstmt3); od_free(desc_copy.data); test(od_global_pstmts_has_pstmt(gm, desc) == 0); od_global_pstmts_map_free(gm); } static void test_pstmt_query_ctx(void) { od_global_pstmt_map_t *gm = od_global_pstmts_map_create(1); /* DISCARD ALL */ od_pstmt_desc_t discard_desc; discard_desc.data = "DISCARD ALL"; discard_desc.len = sizeof("DISCARD ALL"); od_pstmt_t *ps = od_pstmt_create_or_get(gm, discard_desc, &s_arena); test(ps != NULL); test(ps->query_ctx.is_discard_all == 1); test(ps->query_ctx.is_unlisten_all == 0); test(ps->query_ctx.is_deallocate_all == 0); test(ps->query_ctx.has_deallocate_name == 0); od_pstmt_unref(ps); /* UNLISTEN * */ od_pstmt_desc_t unlisten_desc; unlisten_desc.data = "UNLISTEN *"; unlisten_desc.len = sizeof("UNLISTEN *"); ps = od_pstmt_create_or_get(gm, unlisten_desc, &s_arena); test(ps != NULL); test(ps->query_ctx.is_discard_all == 0); test(ps->query_ctx.is_unlisten_all == 1); test(ps->query_ctx.is_deallocate_all == 0); test(ps->query_ctx.has_deallocate_name == 0); od_pstmt_unref(ps); /* DEALLOCATE ALL */ od_pstmt_desc_t dealloc_all_desc; dealloc_all_desc.data = "DEALLOCATE ALL"; dealloc_all_desc.len = sizeof("DEALLOCATE ALL"); ps = od_pstmt_create_or_get(gm, dealloc_all_desc, &s_arena); test(ps != NULL); test(ps->query_ctx.is_discard_all == 0); test(ps->query_ctx.is_unlisten_all == 0); test(ps->query_ctx.is_deallocate_all == 1); test(ps->query_ctx.has_deallocate_name == 0); od_pstmt_unref(ps); /* DEALLOCATE name */ od_pstmt_desc_t dealloc_name_desc; dealloc_name_desc.data = "DEALLOCATE foo"; dealloc_name_desc.len = sizeof("DEALLOCATE foo"); ps = od_pstmt_create_or_get(gm, dealloc_name_desc, &s_arena); test(ps != NULL); test(ps->query_ctx.is_discard_all == 0); test(ps->query_ctx.is_unlisten_all == 0); test(ps->query_ctx.is_deallocate_all == 0); test(ps->query_ctx.has_deallocate_name == 1); test(strcmp(ps->query_ctx.deallocate_name, "foo") == 0); od_pstmt_unref(ps); /* normal SELECT — no flags */ od_pstmt_desc_t select_desc; select_desc.data = "SELECT 1"; select_desc.len = sizeof("SELECT 1"); ps = od_pstmt_create_or_get(gm, select_desc, &s_arena); test(ps != NULL); test(ps->query_ctx.is_discard_all == 0); test(ps->query_ctx.is_unlisten_all == 0); test(ps->query_ctx.is_deallocate_all == 0); test(ps->query_ctx.has_deallocate_name == 0); od_pstmt_unref(ps); od_global_pstmts_map_free(gm); } void test_pstmt_client_hashmap(void) { od_global_pstmt_map_t *global = od_global_pstmts_map_create(1); test(global != NULL); od_client_t *client = od_client_allocate(); client->prep_stmt_ids = od_client_pstmt_hashmap_create(); test(client->prep_stmt_ids != NULL); od_pstmt_desc_t desc1; desc1.data = "data"; desc1.len = sizeof("data"); od_pstmt_desc_t desc2; desc2.data = "data2"; desc2.len = sizeof("data2"); od_pstmt_desc_t desc3; desc3.data = "data3"; desc3.len = sizeof("data3"); od_pstmt_desc_t desc4; desc4.data = "data4"; desc4.len = sizeof("data4"); od_pstmt_desc_t desc5; desc5.data = "data5"; desc5.len = sizeof("data5"); od_pstmt_t *unnamed1 = od_pstmt_create_or_get(global, desc1, &s_arena); test(unnamed1 != NULL); od_pstmt_t *unnamed2 = od_pstmt_create_or_get(global, desc2, &s_arena); test(unnamed2 != NULL); od_pstmt_t *p0 = od_pstmt_create_or_get(global, desc3, &s_arena); test(p0 != NULL); od_pstmt_t *p1 = od_pstmt_create_or_get(global, desc4, &s_arena); test(p1 != NULL); od_pstmt_t *dangling = od_pstmt_create_or_get(global, desc5, &s_arena); test(dangling != NULL); test(unnamed1 != unnamed2); test(unnamed1 != p0); test(unnamed1 != p1); test(unnamed1 != dangling); test(unnamed2 != p0); test(unnamed2 != p1); test(unnamed2 != dangling); test(p0 != p1); test(p0 != dangling); test(p1 != dangling); /* works correctly with unnamed */ test(od_client_has_pstmt(client, "") == 0); test(od_client_get_pstmt(client, "") == NULL); test(od_client_add_pstmt(client, "", unnamed1) == 0); test(od_client_has_pstmt(client, "") == 1); test(od_client_get_pstmt(client, "") == unnamed1); od_pstmt_unref(unnamed1); /* allows to redefine unnamed statement */ test(od_client_add_pstmt(client, "", unnamed2) == 0); test(od_client_has_pstmt(client, "") == 1); test(od_client_get_pstmt(client, "") == unnamed2); od_pstmt_unref(unnamed2); test(od_client_remove_pstmt(client, "") == 0); test(od_client_has_pstmt(client, "") == 0); test(od_client_get_pstmt(client, "") == NULL); /* some named prepared statements */ test(od_client_has_pstmt(client, "P_0") == 0); test(od_client_get_pstmt(client, "P_0") == NULL); test(od_client_has_pstmt(client, "P_0") == 0); test(od_client_get_pstmt(client, "P_0") == NULL); test(od_client_add_pstmt(client, "P_0", p0) == 0); test(od_client_has_pstmt(client, "P_0") == 1); test(od_client_get_pstmt(client, "P_0") == p0); /* do not add twice */ test(od_client_add_pstmt(client, "P_0", p0) == 1); test(od_client_has_pstmt(client, "P_0") == 1); test(od_client_get_pstmt(client, "P_0") == p0); test(od_client_remove_pstmt(client, "P_1") == 0); test(od_client_has_pstmt(client, "P_1") == 0); test(od_client_get_pstmt(client, "P_1") == NULL); test(od_client_add_pstmt(client, "P_1", p1) == 0); test(od_client_has_pstmt(client, "P_1") == 1); test(od_client_get_pstmt(client, "P_1") == p1); test(od_client_remove_pstmt(client, "P_0") == 0); test(od_client_has_pstmt(client, "P_1") == 1); test(od_client_get_pstmt(client, "P_1") == p1); test(od_client_has_pstmt(client, "P_0") == 0); test(od_client_get_pstmt(client, "P_0") == NULL); od_pstmt_unref(p0); od_pstmt_unref(p1); od_client_pstmts_clear(client); test(od_client_has_pstmt(client, "P_1") == 0); test(od_client_get_pstmt(client, "P_1") == NULL); test(od_client_add_pstmt(client, "dangling", dangling) == 0); test(od_client_has_pstmt(client, "dangling") == 1); test(od_client_get_pstmt(client, "dangling") == dangling); od_pstmt_unref(dangling); od_client_free(client); od_global_pstmts_map_free(global); } static void test_portal_client_hashmap(void) { od_global_pstmt_map_t *global = od_global_pstmts_map_create(1); test(global != NULL); od_client_t *client = od_client_allocate(); client->portals = od_client_portal_hashmap_create(); test(client->portals != NULL); od_pstmt_desc_t desc1; desc1.data = "data"; desc1.len = sizeof("data"); od_pstmt_desc_t desc2; desc2.data = "data2"; desc2.len = sizeof("data2"); od_pstmt_desc_t desc3; desc3.data = "data3"; desc3.len = sizeof("data3"); od_pstmt_desc_t desc4; desc4.data = "data4"; desc4.len = sizeof("data4"); od_pstmt_desc_t desc5; desc5.data = "data5"; desc5.len = sizeof("data5"); od_pstmt_t *unnamed1 = od_pstmt_create_or_get(global, desc1, &s_arena); test(unnamed1 != NULL); od_pstmt_t *unnamed2 = od_pstmt_create_or_get(global, desc2, &s_arena); test(unnamed2 != NULL); od_pstmt_t *p0 = od_pstmt_create_or_get(global, desc3, &s_arena); test(p0 != NULL); od_pstmt_t *p1 = od_pstmt_create_or_get(global, desc4, &s_arena); test(p1 != NULL); od_pstmt_t *dangling = od_pstmt_create_or_get(global, desc5, &s_arena); test(dangling != NULL); test(unnamed1 != unnamed2); test(unnamed1 != p0); test(unnamed1 != p1); test(unnamed1 != dangling); test(unnamed2 != p0); test(unnamed2 != p1); test(unnamed2 != dangling); test(p0 != p1); test(p0 != dangling); test(p1 != dangling); /* unnamed portal works */ test(od_client_get_portal(client, "") == NULL); test(od_client_add_portal(client, "", unnamed1) == 0); test(od_client_get_portal(client, "") == unnamed1); od_pstmt_unref(unnamed1); /* portals are always upserted (we are trying our best of tracking) */ test(od_client_add_portal(client, "", unnamed2) == 0); test(od_client_get_portal(client, "") == unnamed2); od_pstmt_unref(unnamed2); test(od_client_remove_portal(client, "") == 0); test(od_client_get_portal(client, "") == NULL); /* named portals */ test(od_client_get_portal(client, "portal_0") == NULL); test(od_client_add_portal(client, "portal_0", p0) == 0); test(od_client_get_portal(client, "portal_0") == p0); /* re-binding the same portal replaces the pstmt */ test(od_client_add_portal(client, "portal_0", p1) == 0); test(od_client_get_portal(client, "portal_0") == p1); test(od_client_add_portal(client, "portal_1", p0) == 0); test(od_client_get_portal(client, "portal_1") == p0); /* remove non-existent is a no-op */ test(od_client_remove_portal(client, "nope") == 0); test(od_client_get_portal(client, "portal_0") == p1); test(od_client_get_portal(client, "portal_1") == p0); test(od_client_remove_portal(client, "portal_0") == 0); test(od_client_get_portal(client, "portal_0") == NULL); test(od_client_get_portal(client, "portal_1") == p0); od_client_portals_clear(client); od_pstmt_unref(p0); od_pstmt_unref(p1); test(od_client_get_portal(client, "portal_1") == NULL); /* dangling entry survives until free */ test(od_client_add_portal(client, "dangling", dangling) == 0); test(od_client_get_portal(client, "dangling") == dangling); od_pstmt_unref(dangling); od_client_free(client); od_global_pstmts_map_free(global); } static void test_pstmt_server_hashmap(void) { od_global_pstmt_map_t *global = od_global_pstmts_map_create(1); test(global != NULL); od_server_t *server = od_server_allocate(1); od_pstmt_desc_t desc3; desc3.data = "data3"; desc3.len = sizeof("data3"); od_pstmt_desc_t desc4; desc4.data = "data4"; desc4.len = sizeof("data4"); od_pstmt_desc_t desc5; desc5.data = "data5"; desc5.len = sizeof("data5"); od_pstmt_t *p0 = od_pstmt_create_or_get(global, desc3, &s_arena); test(p0 != NULL); od_pstmt_t *p1 = od_pstmt_create_or_get(global, desc4, &s_arena); test(p1 != NULL); od_pstmt_t *dangling = od_pstmt_create_or_get(global, desc5, &s_arena); test(dangling != NULL); test(p0 != p1); test(p0 != dangling); test(p1 != dangling); test(od_server_has_pstmt(server, p0) == 0); test(od_server_has_pstmt(server, p1) == 0); test(od_server_remove_pstmt(server, p0) == 0); test(od_server_remove_pstmt(server, p1) == 0); test(od_server_add_pstmt(server, p0) == 0); test(od_server_has_pstmt(server, p0) == 1); test(od_server_has_pstmt(server, p1) == 0); test(od_server_add_pstmt(server, p1) == 0); test(od_server_has_pstmt(server, p0) == 1); test(od_server_has_pstmt(server, p1) == 1); test(od_server_remove_pstmt(server, p1) == 0); test(od_server_has_pstmt(server, p0) == 1); test(od_server_has_pstmt(server, p1) == 0); od_server_pstmts_clear(server); test(od_server_has_pstmt(server, p0) == 0); test(od_server_has_pstmt(server, p1) == 0); od_pstmt_unref(p0); od_pstmt_unref(p1); test(od_server_add_pstmt(server, dangling) == 0); test(od_server_has_pstmt(server, dangling) == 1); od_pstmt_unref(dangling); od_server_free(server); od_global_pstmts_map_free(global); } static void test_pstmt_server_sieve_eviction(void) { od_global_pstmt_map_t *global = od_global_pstmts_map_create(1); test(global != NULL); /* fresh server per sub-test: deterministic hand position */ /* --- pinned statements are never evicted --- */ od_pstmt_desc_t d[4]; static char buf[4][8]; for (int i = 0; i < 4; i++) { snprintf(buf[i], sizeof(buf[i]), "q%d", i); d[i].data = buf[i]; d[i].len = (size_t)strlen(buf[i]) + 1; } od_pstmt_t *p[4]; for (int i = 0; i < 4; i++) { p[i] = od_pstmt_create_or_get(global, d[i], &s_arena); test(p[i] != NULL); } od_server_t *server = od_server_allocate(1); test(server->prep_stmts != NULL); test(server->pstmt_count == 0); machine_msg_t *stream = machine_msg_create(0); test(stream != NULL); test(od_server_pstmt_evict_overflow(server, 10, stream) == 0); test(server->pstmt_count == 0); machine_msg_free(stream); /* refs = 3: pinned */ for (int i = 0; i < 4; i++) { test(od_server_add_pstmt(server, p[i]) == 0); } test(server->pstmt_count == 4); stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 2, stream) == 0); test(server->pstmt_count == 4); machine_msg_free(stream); /* refs = 2: evictable */ for (int i = 0; i < 4; i++) { od_pstmt_unref(p[i]); } stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 0, stream) == 4); test(server->pstmt_count == 0); machine_msg_free(stream); for (int i = 0; i < 4; i++) { test(od_global_pstmts_has_pstmt(global, d[i]) == 0); } od_server_free(server); /* --- oldest unvisited entries are evicted first --- */ for (int i = 0; i < 4; i++) { p[i] = od_pstmt_create_or_get(global, d[i], &s_arena); test(p[i] != NULL); } server = od_server_allocate(1); for (int i = 0; i < 4; i++) { test(od_server_add_pstmt(server, p[i]) == 0); } for (int i = 0; i < 4; i++) { od_pstmt_unref(p[i]); } size_t close_msg_size = 5 + 1 + strlen(p[0]->name) + 1; stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 2, stream) == 2); test(server->pstmt_count == 2); /* p[0], p[1] are freed - checked via the global map only */ test(od_global_pstmts_has_pstmt(global, d[0]) == 0); test(od_global_pstmts_has_pstmt(global, d[1]) == 0); test(od_global_pstmts_has_pstmt(global, d[2]) == 1); test(od_global_pstmts_has_pstmt(global, d[3]) == 1); test(machine_msg_size(stream) == 2 * (int)close_msg_size); char *data = machine_msg_data(stream); test(data[0] == 'C'); test(data[close_msg_size] == 'C'); machine_msg_free(stream); od_server_free(server); /* --- SIEVE: visited entries survive, unvisited are evicted --- */ od_pstmt_desc_t dh[4]; static char hbuf[4][8]; for (int i = 0; i < 4; i++) { snprintf(hbuf[i], sizeof(hbuf[i]), "h%d", i); dh[i].data = hbuf[i]; dh[i].len = (size_t)strlen(hbuf[i]) + 1; } od_pstmt_t *hp[4]; for (int i = 0; i < 4; i++) { hp[i] = od_pstmt_create_or_get(global, dh[i], &s_arena); test(hp[i] != NULL); } server = od_server_allocate(1); for (int i = 0; i < 4; i++) { test(od_server_add_pstmt(server, hp[i]) == 0); } for (int i = 0; i < 4; i++) { od_pstmt_unref(hp[i]); } test(od_server_has_pstmt(server, hp[0]) == 1); stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 2, stream) == 2); test(server->pstmt_count == 2); test(od_server_has_pstmt(server, hp[0]) == 1); test(od_global_pstmts_has_pstmt(global, dh[0]) == 1); test(od_global_pstmts_has_pstmt(global, dh[1]) == 0); test(od_global_pstmts_has_pstmt(global, dh[2]) == 0); test(od_global_pstmts_has_pstmt(global, dh[3]) == 1); machine_msg_free(stream); od_server_free(server); /* --- a pinned statement is skipped and evicted on a later pass --- */ od_pstmt_desc_t dr[4]; static char rbuf[4][8]; for (int i = 0; i < 4; i++) { snprintf(rbuf[i], sizeof(rbuf[i]), "r%d", i); dr[i].data = rbuf[i]; dr[i].len = (size_t)strlen(rbuf[i]) + 1; } od_pstmt_t *rp[4]; for (int i = 0; i < 4; i++) { rp[i] = od_pstmt_create_or_get(global, dr[i], &s_arena); test(rp[i] != NULL); } server = od_server_allocate(1); for (int i = 0; i < 4; i++) { test(od_server_add_pstmt(server, rp[i]) == 0); } for (int i = 0; i < 4; i++) { od_pstmt_unref(rp[i]); } od_pstmt_ref(rp[0]); stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 0, stream) == 3); test(server->pstmt_count == 1); test(od_server_has_pstmt(server, rp[0]) == 1); test(od_global_pstmts_has_pstmt(global, dr[0]) == 1); test(od_global_pstmts_has_pstmt(global, dr[1]) == 0); test(od_global_pstmts_has_pstmt(global, dr[2]) == 0); test(od_global_pstmts_has_pstmt(global, dr[3]) == 0); machine_msg_free(stream); od_pstmt_unref(rp[0]); stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 0, stream) == 1); test(server->pstmt_count == 0); test(od_global_pstmts_has_pstmt(global, dr[0]) == 0); machine_msg_free(stream); od_server_free(server); /* --- clear resets the eviction bookkeeping --- */ od_pstmt_desc_t d4; d4.data = "q4"; d4.len = sizeof("q4"); od_pstmt_t *p4 = od_pstmt_create_or_get(global, d4, &s_arena); test(p4 != NULL); server = od_server_allocate(1); test(od_server_add_pstmt(server, p4) == 0); test(od_server_add_pstmt(server, p4) == 1); test(server->pstmt_count == 1); od_server_pstmts_clear(server); test(server->pstmt_count == 0); stream = machine_msg_create(0); test(od_server_pstmt_evict_overflow(server, 0, stream) == 0); machine_msg_free(stream); od_pstmt_unref(p4); od_server_free(server); od_global_pstmts_map_free(global); } static void test_impl(void *a) { (void)a; od_linear_alloc_init(&s_arena, s_arena_buf, sizeof(s_arena_buf)); test_pstmt_global(); test_refs(); test_pstmt_query_ctx(); test_pstmt_client_hashmap(); test_portal_client_hashmap(); test_pstmt_server_hashmap(); test_pstmt_server_sieve_eviction(); } void odyssey_test_pstmt(void) { machinarium_init(); int64_t rc; rc = machine_create("test_impl", test_impl, NULL); test(rc > 0); test(machine_wait(rc) == 0); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_scram.c000066400000000000000000000023771526064202200225440ustar00rootroot00000000000000#include #include #include #include #include static int read_final_message_with_proof(const char *proof) { char msg[256]; int len = snprintf(msg, sizeof(msg), "c=biws,r=nonce,p=%s", proof); od_scram_state_t state; od_scram_state_init(&state); char *final_nonce; size_t final_nonce_size; uint8_t *client_proof = NULL; int rc = od_scram_read_client_final_message(NULL, &state, msg, len, &final_nonce, &final_nonce_size, &client_proof); if (rc == 0) { od_free(client_proof); } od_scram_state_free(&state); return rc; } static void test_client_final_message_proof_len(void) { /* 32 bytes, the SHA-256 key length */ test(read_final_message_with_proof( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") == 0); /* empty, 3 and 31 bytes */ test(read_final_message_with_proof("") == -1); test(read_final_message_with_proof("AAAA") == -1); test(read_final_message_with_proof( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") == -1); /* 33 bytes */ test(read_final_message_with_proof( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") == -1); } void odyssey_test_scram(void) { test_client_final_message_proof_len(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_sql_parser.c000066400000000000000000000353631526064202200236130ustar00rootroot00000000000000#include #include #include #include #include #define PRINT_BUF_SIZE 1024 #define ARENA_SIZE 8192 static uint8_t s_arena_buf[ARENA_SIZE]; static od_linear_alloc_t s_arena; static void on_error(const char *msg, void *userdata) { strcpy((char *)userdata, msg); } static const char *parse_ok(const char *input) { static char out[PRINT_BUF_SIZE]; static char err[PRINT_BUF_SIZE]; memset(err, 0, sizeof(err)); memset(out, 0, sizeof(out)); od_linear_alloc_reset(&s_arena, 0); od_sql_minimal_node_t *node = od_sql_minimal_parse( input, strlen(input), &s_arena, on_error, err); if (node == NULL || strlen(err) != 0) { fprintf(stderr, "parse_ok failed: input=[%s] err=[s%s] node=%p\n", input, err, (void *)node); abort(); } int n = od_sql_minimal_node_print(node, out, sizeof(out)); test(n > 0 && (size_t)n < sizeof(out)); return out; } static void parse_fail(const char *input) { static char err[PRINT_BUF_SIZE]; memset(err, 0, sizeof(err)); od_linear_alloc_reset(&s_arena, 0); od_sql_minimal_node_t *node = od_sql_minimal_parse( input, strlen(input), &s_arena, on_error, err); if (node != NULL) { fprintf(stderr, "parse_fail: expected failure but got node for [%s]\n", input); abort(); } test(node == NULL); } /* * SHOW tests */ static void test_show_basic(void) { test(strcmp(parse_ok("SHOW search_path"), "(show search_path)") == 0); } static void test_show_all(void) { test(strcmp(parse_ok("SHOW ALL"), "(show all)") == 0); } static void test_show_dotted(void) { test(strcmp(parse_ok("SHOW odyssey.target_session_attrs"), "(show odyssey.target_session_attrs)") == 0); } static void test_show_case_insensitive(void) { test(strcmp(parse_ok("show search_path"), "(show search_path)") == 0); test(strcmp(parse_ok("Show Search_Path"), "(show search_path)") == 0); } static void test_show_with_semicolon(void) { test(strcmp(parse_ok("SHOW search_path;"), "(show search_path)") == 0); } static void test_show_whitespace(void) { test(strcmp(parse_ok(" SHOW search_path "), "(show search_path)") == 0); } static void test_show_with_block_comment(void) { test(strcmp(parse_ok("SHOW /* comment */ search_path"), "(show search_path)") == 0); } static void test_show_multiline_block_comment(void) { test(strcmp(parse_ok("/* multi\nline\ncomment */ SHOW search_path"), "(show search_path)") == 0); } static void test_set_equals(void) { test(strcmp(parse_ok("SET search_path = public"), "(set search_path=public)") == 0); } static void test_set_to(void) { test(strcmp(parse_ok("SET search_path TO public"), "(set search_path=public)") == 0); } static void test_set_string_value(void) { test(strcmp(parse_ok("SET application_name = 'myapp'"), "(set application_name=myapp)") == 0); } static void test_set_default(void) { test(strcmp(parse_ok("SET search_path = DEFAULT"), "(set search_path=default)") == 0); test(strcmp(parse_ok("SET search_path TO DEFAULT"), "(set search_path=default)") == 0); } static void test_set_session_local(void) { test(strcmp(parse_ok("SET SESSION search_path = public"), "(set search_path=public)") == 0); test(strcmp(parse_ok("SET LOCAL search_path = public"), "(set search_path=public)") == 0); } static void test_set_dotted_name(void) { test(strcmp(parse_ok("SET odyssey.target_session_attrs = 'read-only'"), "(set odyssey.target_session_attrs=read-only)") == 0); } static void test_set_boolean_values(void) { test(strcmp(parse_ok("SET enable_seqscan = ON"), "(set enable_seqscan=on)") == 0); test(strcmp(parse_ok("SET enable_seqscan = OFF"), "(set enable_seqscan=off)") == 0); test(strcmp(parse_ok("SET enable_seqscan = TRUE"), "(set enable_seqscan=true)") == 0); test(strcmp(parse_ok("SET enable_seqscan = FALSE"), "(set enable_seqscan=false)") == 0); } static void test_set_integer(void) { test(strcmp(parse_ok("SET work_mem = 4096"), "(set work_mem=4096)") == 0); } static void test_set_with_semicolon(void) { test(strcmp(parse_ok("SET search_path = public;"), "(set search_path=public)") == 0); } static void test_begin_basic(void) { test(strcmp(parse_ok("BEGIN"), "(begin)") == 0); } static void test_begin_work(void) { test(strcmp(parse_ok("BEGIN WORK"), "(begin)") == 0); } static void test_begin_transaction(void) { test(strcmp(parse_ok("BEGIN TRANSACTION"), "(begin)") == 0); } static void test_begin_case_insensitive(void) { test(strcmp(parse_ok("begin"), "(begin)") == 0); test(strcmp(parse_ok("Begin"), "(begin)") == 0); } static void test_begin_with_semicolon(void) { test(strcmp(parse_ok("BEGIN;"), "(begin)") == 0); } static void test_deallocate_name(void) { test(strcmp(parse_ok("DEALLOCATE foo_stmt"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("DEALLOCATE foo_bar"), "(deallocate foo_bar)") == 0); test(strcmp(parse_ok("DEALLOCATE foo123"), "(deallocate foo123)") == 0); } static void test_deallocate_prepare_name(void) { test(strcmp(parse_ok("DEALLOCATE PREPARE foo_stmt"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("DEALLOCATE PREPARE pst"), "(deallocate pst)") == 0); test(strcmp(parse_ok("deallocate prepare pst"), "(deallocate pst)") == 0); } static void test_deallocate_keyword_as_name(void) { /* "preparepst" is not KW_PREPARE — parsed as IDENT */ test(strcmp(parse_ok("DEALLOCATE preparepst"), "(deallocate preparepst)") == 0); test(strcmp(parse_ok("DEALLOCATE PREPAREpst"), "(deallocate preparepst)") == 0); /* "allpst" / "ALLpst" are not KW_ALL — parsed as IDENT */ test(strcmp(parse_ok("DEALLOCATE allpst"), "(deallocate allpst)") == 0); test(strcmp(parse_ok("DEALLOCATE ALLpst"), "(deallocate allpst)") == 0); } static void test_deallocate_all(void) { test(strcmp(parse_ok("DEALLOCATE ALL"), "(deallocate all)") == 0); } static void test_deallocate_prepare_all(void) { test(strcmp(parse_ok("DEALLOCATE PREPARE ALL"), "(deallocate all)") == 0); } static void test_deallocate_case_insensitive(void) { test(strcmp(parse_ok("deallocate foo_stmt"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("Deallocate All"), "(deallocate all)") == 0); } static void test_deallocate_with_semicolon(void) { test(strcmp(parse_ok("DEALLOCATE foo_stmt;"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("DEALLOCATE ALL;"), "(deallocate all)") == 0); test(strcmp(parse_ok("DEALLOCATE foo_stmt; "), "(deallocate foo_stmt)") == 0); } static void test_deallocate_whitespace(void) { test(strcmp(parse_ok(" DEALLOCATE foo_stmt"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("\tDEALLOCATE\tfoo_stmt"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("DEALLOCATE foo_stmt "), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("DEALLOCATE foo_stmt ;"), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok("DEALLOCATE foo_stmt; "), "(deallocate foo_stmt)") == 0); test(strcmp(parse_ok(" \n\r\t DEALLOCATE \n foo_stmt \r\n"), "(deallocate foo_stmt)") == 0); } static void test_deallocate_string_literal(void) { /* * the old od_parse_deallocate accepted string literals * (DEALLOCATE 'foo'); the minimal parser only accepts * identifiers and ALL, so these now fail. */ parse_fail("DEALLOCATE 'foo'"); parse_fail("DEALLOCATE PREPARE 'foo'"); parse_fail("DEALLOCATE 'foo';"); parse_fail("DEALLOCATE 'ALL'"); parse_fail("DEALLOCATE PREPARE 'ALL'"); parse_fail("DEALLOCATE 'a''b'"); parse_fail("DEALLOCATE PREPARE 'a''b'"); parse_fail("DEALLOCATE 'x''y''z';"); parse_fail("DEALLOCATE 'PREPARE'"); } static void test_discard_all(void) { test(strcmp(parse_ok("DISCARD ALL"), "(discard all)") == 0); } static void test_discard_temp(void) { test(strcmp(parse_ok("DISCARD TEMP"), "(discard temp)") == 0); } static void test_discard_temporary(void) { test(strcmp(parse_ok("DISCARD TEMPORARY"), "(discard temp)") == 0); } static void test_discard_plans(void) { test(strcmp(parse_ok("DISCARD PLANS"), "(discard plans)") == 0); } static void test_discard_sequences(void) { test(strcmp(parse_ok("DISCARD SEQUENCES"), "(discard sequences)") == 0); } static void test_discard_case_insensitive(void) { test(strcmp(parse_ok("discard all"), "(discard all)") == 0); test(strcmp(parse_ok("Discard Plans"), "(discard plans)") == 0); } static void test_discard_with_semicolon(void) { test(strcmp(parse_ok("DISCARD ALL;"), "(discard all)") == 0); } static void test_unlisten_name(void) { test(strcmp(parse_ok("UNLISTEN foo"), "(unlisten foo)") == 0); } static void test_unlisten_star(void) { test(strcmp(parse_ok("UNLISTEN *"), "(unlisten *)") == 0); test(strcmp(parse_ok("UNLISTEN \"*\""), "(unlisten *)") == 0); } static void test_unlisten_case_insensitive(void) { test(strcmp(parse_ok("unlisten foo"), "(unlisten foo)") == 0); test(strcmp(parse_ok("Unlisten Foo"), "(unlisten foo)") == 0); } static void test_unlisten_with_semicolon(void) { test(strcmp(parse_ok("UNLISTEN foo;"), "(unlisten foo)") == 0); test(strcmp(parse_ok("UNLISTEN *;"), "(unlisten *)") == 0); } static void test_empty_input(void) { od_linear_alloc_reset(&s_arena, 0); od_sql_minimal_node_t *node = od_sql_minimal_parse("", 0, &s_arena, NULL, NULL); test(node == NULL); } static void test_parse_errors(void) { parse_fail("SELECT 1"); parse_fail("SHOW"); parse_fail("SET foo ="); parse_fail("DEALLOCATE"); parse_fail("DEALLOCATE PREPARE"); parse_fail("DEALLOCATE PREPARE;"); parse_fail("DEALLOCATE PREPARE prepared pst"); parse_fail("DEALLOCATE PREPARED pst"); parse_fail("DEALLOCATE ALL pst"); parse_fail("DEALLOCATE foo_stmt extra"); parse_fail("DISCARD"); parse_fail("UNLISTEN"); parse_fail("COMMIT"); parse_fail("ROLLBACK"); parse_fail("ABORT"); parse_fail("END"); parse_fail("ALLOCATE foo"); parse_fail("PREPARE foo"); } /* * the point is to verify that flex does not call exit(1) when * yy_scan_bytes cannot allocate the buffer copy — instead the parser * should return NULL via longjmp recovery */ static void test_long_query_oom(void) { static char big_query[ARENA_SIZE * 2]; memset(big_query, ' ', sizeof(big_query) - 1); memcpy(big_query, "SELECT ", 7); big_query[sizeof(big_query) - 2] = ';'; big_query[sizeof(big_query) - 1] = '\0'; od_linear_alloc_reset(&s_arena, 0); od_sql_minimal_error_cb_t saved_cb = NULL; (void)saved_cb; od_sql_minimal_node_t *node = od_sql_minimal_parse( big_query, strlen(big_query), &s_arena, NULL, NULL); test(node == NULL); } static void test_long_set_value(void) { static char long_value[512]; memset(long_value, 'x', sizeof(long_value) - 1); long_value[sizeof(long_value) - 1] = '\0'; char input[600]; int n = snprintf(input, sizeof(input), "SET application_name = '%s'", long_value); test(n > 0 && (size_t)n < sizeof(input)); const char *res = parse_ok(input); test(strncmp(res, "(set application_name=", 21) == 0); test(strstr(res, "xxx") != NULL); } static void test_extract_query_ctx(void) { od_linear_alloc_reset(&s_arena, 0); /* DISCARD ALL */ od_sql_minimal_node_t *ast = od_sql_minimal_parse( "DISCARD ALL", strlen("DISCARD ALL"), &s_arena, NULL, NULL); test(ast != NULL); od_query_ctx_t ctx; od_sql_minimal_extract_query_ctx(ast, &ctx); test(ctx.parse_error == 0); test(ctx.is_discard_all == 1); test(ctx.is_unlisten_all == 0); test(ctx.is_deallocate_all == 0); test(ctx.has_deallocate_name == 0); od_query_ctx_reset(&ctx); /* UNLISTEN * */ od_linear_alloc_reset(&s_arena, 0); ast = od_sql_minimal_parse("UNLISTEN *", strlen("UNLISTEN *"), &s_arena, NULL, NULL); test(ast != NULL); od_sql_minimal_extract_query_ctx(ast, &ctx); test(ctx.parse_error == 0); test(ctx.is_discard_all == 0); test(ctx.is_unlisten_all == 1); test(ctx.is_deallocate_all == 0); test(ctx.has_deallocate_name == 0); od_query_ctx_reset(&ctx); /* DEALLOCATE ALL */ od_linear_alloc_reset(&s_arena, 0); ast = od_sql_minimal_parse("DEALLOCATE ALL", strlen("DEALLOCATE ALL"), &s_arena, NULL, NULL); test(ast != NULL); od_sql_minimal_extract_query_ctx(ast, &ctx); test(ctx.parse_error == 0); test(ctx.is_discard_all == 0); test(ctx.is_unlisten_all == 0); test(ctx.is_deallocate_all == 1); test(ctx.has_deallocate_name == 0); od_query_ctx_reset(&ctx); /* DEALLOCATE name */ od_linear_alloc_reset(&s_arena, 0); ast = od_sql_minimal_parse("DEALLOCATE foo", strlen("DEALLOCATE foo"), &s_arena, NULL, NULL); test(ast != NULL); od_sql_minimal_extract_query_ctx(ast, &ctx); test(ctx.parse_error == 0); test(ctx.is_discard_all == 0); test(ctx.is_unlisten_all == 0); test(ctx.is_deallocate_all == 0); test(ctx.has_deallocate_name == 1); test(strcmp(ctx.deallocate_name, "foo") == 0); od_query_ctx_reset(&ctx); test(ctx.has_deallocate_name == 0); /* SELECT — nothing */ od_linear_alloc_reset(&s_arena, 0); ast = od_sql_minimal_parse("SELECT 1", strlen("SELECT 1"), &s_arena, NULL, NULL); test(ast == NULL); /* not recognized by minimal parser */ od_sql_minimal_extract_query_ctx(NULL, &ctx); test(ctx.parse_error == 1); test(ctx.is_discard_all == 0); test(ctx.is_unlisten_all == 0); test(ctx.is_deallocate_all == 0); test(ctx.has_deallocate_name == 0); od_query_ctx_reset(&ctx); /* DISCARD TEMP — not ALL, no parse error */ od_linear_alloc_reset(&s_arena, 0); ast = od_sql_minimal_parse("DISCARD TEMP", strlen("DISCARD TEMP"), &s_arena, NULL, NULL); test(ast != NULL); od_sql_minimal_extract_query_ctx(ast, &ctx); test(ctx.parse_error == 0); test(ctx.is_discard_all == 0); od_query_ctx_reset(&ctx); } void odyssey_test_sql_minimal_parser(void) { od_linear_alloc_init(&s_arena, s_arena_buf, sizeof(s_arena_buf)); test_show_basic(); test_show_all(); test_show_dotted(); test_show_case_insensitive(); test_show_with_semicolon(); test_show_whitespace(); test_show_with_block_comment(); test_show_multiline_block_comment(); test_set_equals(); test_set_to(); test_set_string_value(); test_set_default(); test_set_session_local(); test_set_dotted_name(); test_set_boolean_values(); test_set_integer(); test_set_with_semicolon(); test_begin_basic(); test_begin_work(); test_begin_transaction(); test_begin_case_insensitive(); test_begin_with_semicolon(); test_deallocate_name(); test_deallocate_prepare_name(); test_deallocate_all(); test_deallocate_prepare_all(); test_deallocate_case_insensitive(); test_deallocate_with_semicolon(); test_deallocate_whitespace(); test_deallocate_keyword_as_name(); test_deallocate_string_literal(); test_discard_all(); test_discard_temp(); test_discard_temporary(); test_discard_plans(); test_discard_sequences(); test_discard_case_insensitive(); test_discard_with_semicolon(); test_unlisten_name(); test_unlisten_star(); test_unlisten_case_insensitive(); test_unlisten_with_semicolon(); test_empty_input(); test_parse_errors(); test_long_query_oom(); test_long_set_value(); test_extract_query_ctx(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_tdigest.c000066400000000000000000000114041526064202200230710ustar00rootroot00000000000000#include #include #include #include #include void simple_test(void) { td_histogram_t *histogram = td_new(100); td_add(histogram, 1, 1); td_add(histogram, 2, 1); test(td_value_at(histogram, 0) == 1); test(td_value_at(histogram, .5) == 1.5); test(td_value_at(histogram, 1) == 2); td_histogram_t *h2 = td_new(100); td_add(h2, 0, 1); td_add(h2, 3, 1); td_merge(histogram, h2); test(td_value_at(histogram, 0) == 0); test(td_value_at(histogram, .5) == 1.5); test(td_value_at(histogram, 1) == 3); td_free(h2); td_free(histogram); } void monotonicity_test(void) { td_histogram_t *histogram = td_new(100); unsigned short xseed[3] = { 123, 42, 21 }; for (size_t i = 0; i < 100000; ++i) { td_add(histogram, machine_erand48(xseed), 1); } double last_quantile = -1; double last_x = -1; for (double i = 0; i <= 1; i += 1e-5) { double current_x = td_value_at(histogram, i); test(current_x >= last_x); last_x = current_x; double current_quantile = td_quantile_of(histogram, i); test(current_quantile >= last_quantile); } td_safe_free(histogram); } void extreme_quantiles_test(void) { td_histogram_t *histogram = td_new(100); td_add(histogram, 10, 3); td_add(histogram, 20, 1); td_add(histogram, 40, 5); #define size 9 double expected[size] = { 5., 10., 15., 20., 30., 35., 40., 45., 50. }; double quantiles[3] = { 1.5 / size, 3.5 / size, 6.5 / size }; for (size_t i = 0; i < 3; ++i) { double quantile = quantiles[i]; size_t index = floor(quantile * size); test(fabs(td_value_at(histogram, quantile) - expected[index]) < 0.01); } td_safe_free(histogram); } void three_point_test(void) { td_histogram_t *histogram = td_new(100); double x0 = 0.18615591526031494; double x1 = 0.4241943657398224; double x2 = 0.8813006281852722; td_add(histogram, x0, 1); td_add(histogram, x1, 1); td_add(histogram, x2, 1); double p10 = td_value_at(histogram, 0.1); double p50 = td_value_at(histogram, 0.5); double p90 = td_value_at(histogram, 0.9); double p95 = td_value_at(histogram, 0.95); double p99 = td_value_at(histogram, 0.99); test(p10 <= p50); test(p50 <= p90); test(p90 <= p95); test(p95 <= p99); test(x0 == p10); test(x2 == p99); td_safe_free(histogram); } void merge_several_digests_test(void) { td_histogram_t *hists[5]; for (size_t i = 0; i < 5; ++i) { hists[i] = td_new(100); } for (size_t i = 0; i < 1000; ++i) { td_add(hists[0], i, 1); } td_histogram_t *common_hist = td_new(100); for (size_t i = 0; i < 5; ++i) { td_merge(common_hist, hists[i]); } double quantiles[3] = { 0.5, 0.9, 0.99 }; for (size_t i = 0; i < 3; ++i) { test(fabs(td_value_at(common_hist, quantiles[i]) - td_value_at(hists[0], quantiles[i])) < 1e-6); } for (size_t i = 0; i < 5; ++i) { td_safe_free(hists[i]); } td_safe_free(common_hist); } void tdigest_forward_test(void); void tdigest_backward_test(void); int tdigest_random_test(void); void machinarium_test_tdigest(void) { machinarium_init(); machine_lrand48_seed(); tdigest_forward_test(); tdigest_backward_test(); int fails = 0; for (int i = 0; i < 20; i++) { fails += tdigest_random_test(); } /* fails approx 1/1000, so suppress flaps to impossible */ test(fails <= 3); machinarium_free(); } int tdigest_random_test(void) { td_histogram_t *histogram = td_new(100); td_histogram_t *freeze = td_new(100); for (int i = 0; i < 100000; i++) { td_add(histogram, machine_lrand48() % 10000, 1); } td_copy(freeze, histogram); int result = 0; if ((int)round(td_value_at(freeze, 0.8) / 2000.0) != 4) { result++; } if ((int)round(td_value_at(freeze, 0.6) / 2000.0) != 3) { result++; } if ((int)round(td_value_at(freeze, 0.4) / 2000.0) != 2) { result++; } td_safe_free(histogram); td_safe_free(freeze); return result; } void tdigest_backward_test(void) { td_histogram_t *histogram = td_new(100); td_histogram_t *freeze = td_new(100); for (int i = 1; i <= 100; i++) { td_add(histogram, 100 - i, 1); } td_copy(freeze, histogram); test(fabs(td_value_at(freeze, 0.7) - 70) < 1); test(fabs(td_value_at(freeze, 0.5) - 50) < 1); test(fabs(td_value_at(freeze, 0.3) - 30) < 1); td_free(histogram); td_free(freeze); } void tdigest_forward_test(void) { td_histogram_t *histogram = td_new(100); td_histogram_t *freeze = td_new(100); for (int i = 1; i <= 100; i++) { td_add(histogram, i, 1); } td_copy(freeze, histogram); test(fabs(td_value_at(freeze, 0.7) - 70) < 1); test(fabs(td_value_at(freeze, 0.5) - 50) < 1); test(fabs(td_value_at(freeze, 0.3) - 30) < 1); td_free(histogram); td_free(freeze); } void odyssey_test_tdigest(void) { simple_test(); monotonicity_test(); extreme_quantiles_test(); three_point_test(); merge_several_digests_test(); machinarium_test_tdigest(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_thread_pool.c000066400000000000000000000064371526064202200237400ustar00rootroot00000000000000#include #include #include #include #include #include typedef struct { mm_wait_flag_t *flag; machine_wait_group_t *group; } blocking_arg_t; static void *calc(void *a) { atomic_size_t *arg = a; for (size_t i = 0; i < 1000; ++i) { atomic_fetch_add(arg, 1); if (i % 50 == 0) { machine_sleep(1); } } return NULL; } static void test_integral(void *arg) { (void)arg; /* * 100 tasks which computes 1000 atomic incs */ atomic_size_t value; atomic_init(&value, 0); size_t n = 100; od_future_t **futures = od_malloc(n * sizeof(od_future_t *)); test(futures != NULL); memset(futures, 0, n * sizeof(od_future_t *)); od_thread_pool_t pool; test(od_thread_pool_init(&pool, "integrator", 5, 100) == 0); for (size_t i = 0; i < n; ++i) { futures[i] = od_thread_pool_submit(&pool, calc, &value, NULL, NULL, 0); test(futures[i] != NULL); } for (size_t i = 0; i < n; ++i) { test(od_thread_pool_wait(futures[i], 60 * 1000) == 0); od_future_unref(futures[i]); } test(atomic_load(&value) == (n * 1000)); od_free(futures); od_thread_pool_destroy(&pool); } static void *sleepy(void *a) { (void)a; machine_sleep(1000); return NULL; } static void *blocking_task(void *a) { blocking_arg_t *arg = a; machine_wait_group_done(arg->group); mm_wait_flag_wait(arg->flag, UINT32_MAX); return NULL; } static void test_queue_full(void *arg) { (void)arg; size_t n = 4; od_future_t **futures = od_malloc(n * sizeof(od_future_t *)); test(futures != NULL); memset(futures, 0, n * sizeof(od_future_t *)); od_thread_pool_t pool; test(od_thread_pool_init(&pool, "sleeper", 2, 2) == 0); machine_wait_group_t *wait_group = machine_wait_group_create(); test(wait_group != NULL); mm_wait_flag_t *blocking_flag = mm_wait_flag_create(); test(blocking_flag != NULL); blocking_arg_t blocking_arg; blocking_arg.flag = blocking_flag; blocking_arg.group = wait_group; machine_wait_group_add(wait_group); futures[0] = od_thread_pool_submit(&pool, blocking_task, &blocking_arg, NULL, NULL, 0); test(futures[0] != NULL); machine_wait_group_add(wait_group); futures[1] = od_thread_pool_submit(&pool, blocking_task, &blocking_arg, NULL, NULL, 0); test(futures[1] != NULL); test(machine_wait_group_wait(wait_group, 60 * 1000) == 0); futures[2] = od_thread_pool_submit(&pool, sleepy, NULL, NULL, NULL, 0); test(futures[2] != NULL); futures[3] = od_thread_pool_submit(&pool, sleepy, NULL, NULL, NULL, 0); test(futures[3] != NULL); od_future_t *f = od_thread_pool_submit(&pool, sleepy, NULL, NULL, NULL, 0); test(f == NULL); test(mm_errno_get() == EAGAIN); mm_wait_flag_set(blocking_flag); for (size_t i = 0; i < n; ++i) { test(od_thread_pool_wait(futures[i], 60 * 1000) == 0); od_future_unref(futures[i]); } od_free(futures); od_thread_pool_destroy(&pool); machine_wait_group_destroy(wait_group); mm_wait_flag_destroy(blocking_flag); } void odyssey_test_thread_pool(void) { machinarium_init(); int64_t rc; rc = machine_create("test_integral", test_integral, NULL); test(rc > 0); test(machine_wait(rc) == 0); rc = machine_create("test_queue_full", test_queue_full, NULL); test(rc > 0); test(machine_wait(rc) == 0); machinarium_free(); } odyssey-1.6.0-rc1/sources/tests/odyssey/test_util.c000066400000000000000000000020221526064202200223770ustar00rootroot00000000000000#include #include #include void test_od_memtol_sanity(void) { char str[] = " \t 42 +12\t-17 -0 +0"; size_t data_size = strlen(str); char *data = malloc(data_size); memcpy(data, str, data_size); char *ptr = data; long value; value = od_memtol(ptr, data_size, &ptr, 10); test(value == 42); test(memcmp(data, " \t 42", ptr - data) == 0); value = od_memtol(ptr, data_size - (ptr - data), &ptr, 10); test(value == 12); test(memcmp(data, " \t 42 +12", ptr - data) == 0); value = od_memtol(ptr, data_size - (ptr - data), &ptr, 10); test(value == -17); test(memcmp(data, " \t 42 +12\t-17", ptr - data) == 0); value = od_memtol(ptr, data_size - (ptr - data), &ptr, 10); test(value == 0); test(memcmp(data, " \t 42 +12\t-17 -0", ptr - data) == 0); value = od_memtol(ptr, data_size - (ptr - data), &ptr, 10); test(value == 0); test(memcmp(data, " \t 42 +12\t-17 -0 +0", ptr - data) == 0); free(data); } void odyssey_test_util(void) { test_od_memtol_sanity(); } odyssey-1.6.0-rc1/sources/tests/odyssey_test.c000066400000000000000000000257761526064202200214470ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include char test_substring[1024] = { 0 }; /* KIWI */ extern void kiwi_test_enquote(void); extern void kiwi_test_pgoptions(void); extern void kiwi_test_be_read_startup_options(void); extern void kiwi_test_fe_read_auth(void); extern void kiwi_test_be_read_query(void); /* MACHINARIUM */ extern void machinarium_test_init(void); extern void machinarium_test_create0(void); extern void machinarium_test_create1(void); extern void machinarium_test_config(void); extern void machinarium_test_context_switch(void); extern void machinarium_test_sleep(void); extern void machinarium_test_sleep_random(void); extern void machinarium_test_sleep_yield(void); extern void machinarium_test_sleep_cancel0(void); extern void machinarium_test_join(void); extern void machinarium_test_condition0(void); extern void machinarium_test_stat(void); extern void machinarium_test_signal0(void); extern void machinarium_test_signal1(void); extern void machinarium_test_channel_create(void); extern void machinarium_test_channel_rw0(void); extern void machinarium_test_channel_rw1(void); extern void machinarium_test_channel_rw2(void); extern void machinarium_test_channel_rw3(void); extern void machinarium_test_channel_rw4(void); extern void machinarium_test_channel_timeout(void); extern void machinarium_test_channel_cancel(void); extern void machinarium_test_channel_shared_create(void); extern void machinarium_test_channel_shared_rw0(void); extern void machinarium_test_channel_shared_rw1(void); extern void machinarium_test_channel_shared_rw2(void); extern void machinarium_test_sleeplock(void); extern void machinarium_test_producer_consumer0(void); extern void machinarium_test_producer_consumer1(void); extern void machinarium_test_producer_consumer2(void); extern void machinarium_test_io_new(void); extern void machinarium_test_connect(void); extern void machinarium_test_connect_timeout(void); extern void machinarium_test_connect_cancel0(void); extern void machinarium_test_connect_cancel1(void); extern void machinarium_test_coroutine_names(void); extern void machinarium_test_accept_timeout(void); extern void machinarium_test_accept_cancel(void); extern void machinarium_test_advice_keepalive_usr_timeout(void); extern void machinarium_test_getaddrinfo0(void); extern void machinarium_test_getaddrinfo1(void); extern void machinarium_test_getaddrinfo2(void); extern void machinarium_test_client_server0(void); extern void machinarium_test_client_server1(void); extern void machinarium_test_client_server2(void); extern void machinarium_test_client_server_unix_socket(void); extern void machinarium_test_client_server_unix_socket_no_msg(void); extern void machinarium_test_read_10mb0(void); extern void machinarium_test_read_10mb1(void); extern void machinarium_test_read_10mb2(void); extern void machinarium_test_read_timeout(void); extern void machinarium_test_read_cancel(void); extern void machinarium_test_read_var(void); extern void machinarium_test_hm_hash(void); extern void machinarium_test_tls0(void); extern void machinarium_test_tls_verify_error(void); extern void machinarium_test_tls_cache(void); extern void machinarium_test_tls_unix_socket_no_msg(void); extern void machinarium_test_tls_unix_socket(void); extern void machinarium_test_tls_read_10mb0(void); extern void machinarium_test_tls_read_10mb1(void); extern void machinarium_test_tls_read_10mb2(void); extern void machinarium_test_tls_read_multithread(void); extern void machinarium_test_tls_read_var(void); extern void machinarium_test_wait_list_compare_wait_timeout(void); extern void machinarium_test_wait_list_compare_wait_wrong_value(void); extern void machinarium_test_wait_list_notify_after_compare_wait(void); extern void machinarium_test_wait_list_without_notify(void); extern void machinarium_test_wait_list_notify_after_wait(void); extern void machinarium_test_wait_list_one_producer_multiple_consumers(void); extern void machinarium_test_wait_list_one_producer_multiple_consumers_threads(void); extern void machinarium_test_wait_list_notify_all(void); extern void machinarium_test_wait_group_simple(void); extern void machinarium_test_wait_group_timeout(void); extern void machinarium_test_wait_group_lifetime(void); extern void machinarium_test_wait_flag_simple(void); extern void machinarium_test_wait_flag_timeout(void); extern void machinarium_test_ring_buffer(void); extern void machinarium_test_vrb(void); extern void machinarium_test_heap(void); extern void machinarium_test_lf_stack(void); extern void machinarium_test_queue(void); extern void machinarium_vrb_benchmark(void); extern void machinarium_test_eventfd(void); extern void machinarium_test_sem(void); extern void machinarium_test_mutex_threads(void); extern void machinarium_test_mutex_coroutines(void); extern void machinarium_test_mutex_timeout(void); extern void odyssey_test_tdigest(void); extern void odyssey_test_attribute(void); extern void odyssey_test_util(void); extern void odyssey_test_hba(void); extern void odyssey_test_address_parse(void); extern void odyssey_test_address_cmp(void); extern void odyssey_test_pstmt(void); extern void odyssey_test_affinity(void); extern void odyssey_test_duration_parse(void); extern void odyssey_test_thread_pool(void); extern void odyssey_test_linear_alloc(void); extern void odyssey_test_scram(void); extern void odyssey_test_sql_minimal_parser(void); extern void odyssey_test_console_parser(void); extern void machinarium_test_tsan_simple_race_example(void); int main(int argc, char *argv[]) { if (argc > 1) { odyssey_test_set_test_substring(argv[1]); } /* * Normally smth like that is done by odyssey/machinarium itself. * But this test doesn't have signal handlers and * machinarium's mm_socket_set_nosigpipe will not work on linux. * So this SIGPIPE ignoring will fix some tests (like tls unix socket with no msg). */ signal(SIGPIPE, SIG_IGN); odyssey_test(kiwi_test_enquote); odyssey_test(kiwi_test_pgoptions); odyssey_test(kiwi_test_be_read_startup_options); odyssey_test(kiwi_test_fe_read_auth); odyssey_test(kiwi_test_be_read_query); odyssey_test(machinarium_test_init); odyssey_test(machinarium_test_create0); odyssey_test(machinarium_test_create1); odyssey_test(machinarium_test_config); odyssey_test(machinarium_test_context_switch); odyssey_test(machinarium_test_sleep); odyssey_test(machinarium_test_sleep_random); odyssey_test(machinarium_test_sleep_yield); odyssey_test(machinarium_test_sleep_cancel0); odyssey_test(machinarium_test_join); odyssey_test(machinarium_test_condition0); odyssey_test(machinarium_test_stat); odyssey_test(machinarium_test_signal0); odyssey_test(machinarium_test_signal1); odyssey_test(machinarium_test_channel_create); odyssey_test(machinarium_test_channel_rw0); odyssey_test(machinarium_test_channel_rw1); odyssey_test(machinarium_test_channel_rw2); odyssey_test(machinarium_test_channel_rw3); odyssey_test(machinarium_test_channel_rw4); odyssey_test(machinarium_test_channel_timeout); odyssey_test(machinarium_test_channel_cancel); odyssey_test(machinarium_test_channel_shared_create); odyssey_test(machinarium_test_channel_shared_rw0); odyssey_test(machinarium_test_channel_shared_rw1); odyssey_test(machinarium_test_channel_shared_rw2); odyssey_test(machinarium_test_sleeplock); odyssey_test(machinarium_test_producer_consumer0); odyssey_test(machinarium_test_producer_consumer1); odyssey_test(machinarium_test_producer_consumer2); odyssey_test(machinarium_test_io_new); odyssey_test(machinarium_test_connect); odyssey_test(machinarium_test_connect_timeout); odyssey_test(machinarium_test_connect_cancel0); odyssey_test(machinarium_test_connect_cancel1); odyssey_test(machinarium_test_accept_timeout); odyssey_test(machinarium_test_accept_cancel); odyssey_test(machinarium_test_advice_keepalive_usr_timeout); odyssey_test(machinarium_test_getaddrinfo0); odyssey_test(machinarium_test_getaddrinfo1); odyssey_test(machinarium_test_getaddrinfo2); odyssey_test(machinarium_test_client_server0); odyssey_test(machinarium_test_client_server1); odyssey_test(machinarium_test_client_server2); odyssey_test(machinarium_test_client_server_unix_socket); odyssey_test(machinarium_test_client_server_unix_socket_no_msg); odyssey_test(machinarium_test_coroutine_names); odyssey_test(machinarium_test_read_10mb0); odyssey_test(machinarium_test_read_10mb1); odyssey_test(machinarium_test_read_10mb2); odyssey_test(machinarium_test_read_timeout); odyssey_test(machinarium_test_read_cancel); odyssey_test(machinarium_test_read_var); odyssey_test(machinarium_test_hm_hash); odyssey_test(machinarium_test_tls0); odyssey_test(machinarium_test_tls_verify_error); odyssey_test(machinarium_test_tls_cache); odyssey_test(machinarium_test_tls_unix_socket_no_msg); odyssey_test(machinarium_test_tls_unix_socket); odyssey_test(machinarium_test_tls_read_10mb0); odyssey_test(machinarium_test_tls_read_10mb1); odyssey_test(machinarium_test_tls_read_10mb2); odyssey_test(machinarium_test_tls_read_multithread); odyssey_test(machinarium_test_tls_read_var); odyssey_test(machinarium_test_wait_list_compare_wait_timeout); odyssey_test(machinarium_test_wait_list_notify_after_compare_wait); odyssey_test(machinarium_test_wait_list_compare_wait_wrong_value); odyssey_test(machinarium_test_wait_list_without_notify); odyssey_test(machinarium_test_wait_list_notify_after_wait); odyssey_test( machinarium_test_wait_list_one_producer_multiple_consumers); odyssey_test( machinarium_test_wait_list_one_producer_multiple_consumers_threads); odyssey_test(machinarium_test_wait_list_notify_all); odyssey_test(machinarium_test_wait_group_simple); odyssey_test(machinarium_test_wait_group_timeout); odyssey_test(machinarium_test_wait_group_lifetime); odyssey_test(machinarium_test_wait_flag_simple); odyssey_test(machinarium_test_wait_flag_timeout); odyssey_test(machinarium_test_ring_buffer); odyssey_test(machinarium_test_vrb); odyssey_test(machinarium_test_queue); odyssey_test(machinarium_test_heap); odyssey_test(machinarium_test_lf_stack); odyssey_playground_test(machinarium_vrb_benchmark); odyssey_test(machinarium_test_mutex_threads); odyssey_test(machinarium_test_mutex_coroutines); odyssey_test(machinarium_test_mutex_timeout); odyssey_test(machinarium_test_eventfd); odyssey_test(machinarium_test_sem); odyssey_test(odyssey_test_tdigest); odyssey_test(odyssey_test_attribute); odyssey_test(odyssey_test_util); odyssey_test(odyssey_test_hba); odyssey_test(odyssey_test_address_parse); odyssey_test(odyssey_test_address_cmp); odyssey_test(odyssey_test_pstmt); odyssey_test(odyssey_test_affinity); odyssey_test(odyssey_test_duration_parse); odyssey_test(odyssey_test_thread_pool); odyssey_test(odyssey_test_linear_alloc); odyssey_test(odyssey_test_scram); odyssey_test(odyssey_test_sql_minimal_parser); odyssey_test(odyssey_test_console_parser); odyssey_playground_test(machinarium_test_tsan_simple_race_example); return 0; } odyssey-1.6.0-rc1/sources/thread_global.c000066400000000000000000000015161526064202200203200ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include od_retcode_t od_thread_global_init(od_thread_global **gl) { *gl = od_malloc(sizeof(od_thread_global)); if (*gl == NULL) { return NOT_OK_RESPONSE; } if (od_conn_eject_info_init( &(*gl)->info, &od_global_get_instance()->config.conn_drop_options) != 0) { od_free(*gl); return NOT_OK_RESPONSE; } return OK_RESPONSE; } od_thread_global **od_thread_global_get(void) { return (od_thread_global **)machine_thread_private(); } void od_thread_global_free(void *arg) { od_thread_global *gl = arg; od_retcode_t rc = od_conn_eject_info_free(gl->info); if (rc != OK_RESPONSE) { return; } od_free(gl); } odyssey-1.6.0-rc1/sources/thread_pool.c000066400000000000000000000116621526064202200200340ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include od_future_t *od_future_create(od_thread_pool_result_dtor_t dtor) { od_future_t *f = od_malloc(sizeof(od_future_t)); if (f == NULL) { return f; } memset(f, 0, sizeof(od_future_t)); atomic_init(&f->value, (uintptr_t)NULL); atomic_init(&f->refs, 1); f->value_dtor = dtor; f->wait = mm_wait_flag_create(); if (f->wait == NULL) { od_free(f); return NULL; } return f; } void od_future_ref(od_future_t *future) { atomic_fetch_add(&future->refs, 1); } void od_future_unref(od_future_t *future) { uint64_t r = atomic_fetch_sub(&future->refs, 1); if (r == 1) { void *value = od_future_get_result(future); if (future->value_dtor != NULL && value != NULL) { future->value_dtor(value); } mm_wait_flag_destroy(future->wait); od_free(future); } } void *od_future_get_result(od_future_t *future) { void *v = (void *)atomic_load(&future->value); return v; } static void task_destroy(void *t) { od_thread_pool_task_t *task = t; if (task->future != NULL) { od_future_unref(task->future); } } static void tp_work(void *arg) { od_thread_pool_worker_t *worker = arg; od_thread_pool_t *pool = worker->pool; while (1) { uint64_t version = atomic_load(&pool->version); od_thread_pool_task_t task; if (mm_queue_pop(&pool->queue, &task)) { if (task.fn == NULL) { break; } if (task.detached || atomic_load(&task.future->refs) > 1) { /* * the user might have give up waiting the task * lets execute task only if user is awaiting the result */ void *result = task.fn(task.arg); atomic_store(&task.future->value, (uintptr_t)result); mm_wait_flag_set(task.future->wait); } if (task.arg_dtor) { task.arg_dtor(task.arg); } od_future_unref(task.future); continue; } mm_wait_list_compare_wait(&pool->notifier, NULL, version, 1000); } } static void notify_one(od_thread_pool_t *pool) { atomic_fetch_add(&pool->version, 1); mm_wait_list_notify(&pool->notifier); } static void stop(od_thread_pool_t *pool) { uint64_t exp = 0; if (!atomic_compare_exchange_strong(&pool->stop, &exp, 1)) { return; } for (size_t i = 0; i < pool->size; ++i) { od_thread_pool_task_t cl; cl.future = NULL; cl.arg = NULL; cl.fn = NULL; while (!mm_queue_push(&pool->queue, &cl)) { /* * TODO: do it better? * * no place in queue to submit the close task * just wait for slot */ machine_sleep(1); } notify_one(pool); } } int od_thread_pool_init(od_thread_pool_t *pool, const char *name, size_t size, size_t queue_size) { memset(pool, 0, sizeof(od_thread_pool_t)); strncpy(pool->name, name, sizeof(pool->name) - 1); int rc = mm_queue_init(&pool->queue, queue_size, sizeof(od_thread_pool_task_t), task_destroy); if (rc != 0) { return rc; } pool->size = size; atomic_init(&pool->version, 0); atomic_init(&pool->stop, 0); mm_wait_list_init(&pool->notifier, &pool->version); pool->workers = od_malloc(size * sizeof(od_thread_pool_worker_t)); if (pool->workers == NULL) { mm_wait_list_destroy(&pool->notifier); mm_queue_destroy(&pool->queue); return -1; } memset(pool->workers, 0, size * sizeof(od_thread_pool_worker_t)); for (size_t i = 0; i < size; ++i) { od_thread_pool_worker_t *wrk = &pool->workers[i]; wrk->pool = pool; wrk->idx = i; od_snprintf(wrk->name, sizeof(wrk->name), "%s_%lu", name, i); wrk->machine_id = machine_create(wrk->name, tp_work, wrk); if (wrk->machine_id == -1) { pool->size = i; od_thread_pool_destroy(pool); return -1; } } return 0; } void od_thread_pool_destroy(od_thread_pool_t *pool) { stop(pool); for (size_t j = 0; j < pool->size; ++j) { machine_wait(pool->workers[j].machine_id); } od_free(pool->workers); mm_queue_destroy(&pool->queue); mm_wait_list_destroy(&pool->notifier); } od_future_t *od_thread_pool_submit(od_thread_pool_t *pool, od_thread_pool_task_fn_t fn, void *arg, od_thread_pool_task_arg_dtor_t arg_dtor, od_thread_pool_result_dtor_t result_dtor, int detach) { if (atomic_load(&pool->stop)) { mm_errno_set(ENOTCONN); return NULL; } od_future_t *future = od_future_create(result_dtor); if (future == NULL) { return NULL; } od_thread_pool_task_t task; memset(&task, 0, sizeof(od_thread_pool_task_t)); task.future = future; task.fn = fn; task.arg = arg; task.detached = detach; task.arg_dtor = arg_dtor; /* one another ref for 'user' */ od_future_ref(future); if (mm_queue_push(&pool->queue, &task)) { notify_one(pool); return future; } /* no actual 'user' exists, so double-unref */ od_future_unref(future); od_future_unref(future); mm_errno_set(EAGAIN); return NULL; } int od_thread_pool_wait(od_future_t *future, uint32_t timeout_ms) { int rc = mm_wait_flag_wait(future->wait, timeout_ms); return rc; } odyssey-1.6.0-rc1/sources/tls.c000066400000000000000000000154101526064202200163310ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include machine_tls_t *od_tls_frontend(od_config_listen_t *config) { int rc; machine_tls_t *tls; tls = machine_tls_create(); if (tls == NULL) { return NULL; } switch (config->tls_opts->tls_mode) { case OD_CONFIG_TLS_ALLOW: case OD_CONFIG_TLS_PREFER: machine_tls_set_verify(tls, "none"); break; case OD_CONFIG_TLS_REQUIRE: machine_tls_set_verify(tls, "peer"); break; default: machine_tls_set_verify(tls, "peer_strict"); break; } if (config->tls_opts->tls_ca_file) { rc = machine_tls_set_ca_file(tls, config->tls_opts->tls_ca_file); if (rc == -1) { machine_tls_free(tls); return NULL; } } if (config->tls_opts->tls_cert_file) { rc = machine_tls_set_cert_file(tls, config->tls_opts->tls_cert_file); if (rc == -1) { machine_tls_free(tls); return NULL; } } if (config->tls_opts->tls_key_file) { rc = machine_tls_set_key_file(tls, config->tls_opts->tls_key_file); if (rc == -1) { machine_tls_free(tls); return NULL; } } return tls; } /* * Load the configured certificate material the same way the TLS handshake * does. Runs before machinarium is initialized, so it must not use any * machine_* call. */ od_retcode_t od_tls_frontend_validate(od_config_listen_t *config, char *error, int error_len) { od_tls_opts_t *opts = config->tls_opts; const char *failed = NULL; od_retcode_t rc = NOT_OK_RESPONSE; SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method()); if (ctx == NULL) { failed = "SSL_CTX_new()"; goto done; } if (opts->tls_cert_file && !SSL_CTX_use_certificate_chain_file(ctx, opts->tls_cert_file)) { failed = "SSL_CTX_use_certificate_chain_file()"; goto done; } if (opts->tls_key_file && SSL_CTX_use_PrivateKey_file(ctx, opts->tls_key_file, SSL_FILETYPE_PEM) != 1) { failed = "SSL_CTX_use_PrivateKey_file()"; goto done; } if (opts->tls_cert_file && opts->tls_key_file && SSL_CTX_check_private_key(ctx) != 1) { failed = "SSL_CTX_check_private_key()"; goto done; } if (opts->tls_ca_file && SSL_CTX_load_verify_locations(ctx, opts->tls_ca_file, NULL) != 1) { failed = "SSL_CTX_load_verify_locations()"; goto done; } rc = OK_RESPONSE; done: if (rc != OK_RESPONSE && error != NULL && error_len > 0) { unsigned long err = ERR_get_error(); od_snprintf(error, error_len, "%s: %s", failed, err ? ERR_error_string(err, NULL) : "unknown error"); } if (ctx != NULL) { SSL_CTX_free(ctx); } return rc; } int od_tls_frontend_accept(od_client_t *client, od_logger_t *logger, od_config_listen_t *config, machine_tls_t *tls) { od_debug(logger, "tls", client, NULL, "ssl request"); int rc; if (config->tls_opts->tls_mode == OD_CONFIG_TLS_DISABLE) { /* not supported 'N' */ machine_msg_t *msg; msg = machine_msg_create(sizeof(uint8_t)); if (msg == NULL) { return -1; } uint8_t *type = machine_msg_data(msg); *type = 'N'; rc = od_write(&client->io, msg); if (rc == -1) { od_error(logger, "tls", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } od_debug(logger, "tls", client, NULL, "is disabled, ignoring"); return 0; } /* supported 'S' */ machine_msg_t *msg; msg = machine_msg_create(sizeof(uint8_t)); if (msg == NULL) { return -1; } uint8_t *type = machine_msg_data(msg); *type = 'S'; rc = od_write(&client->io, msg); if (rc == -1) { od_error(logger, "tls", client, NULL, "write error: %s", od_io_error(&client->io)); return -1; } if (od_readahead_unread(&client->io.readahead) > 0) { od_error(logger, "tls", client, NULL, "extraneous data from client"); return -1; /* prevent possible buffer, protecting against CVE-2021-23214-like attacks */ } rc = mm_io_set_tls(client->io.io, tls, config->client_login_timeout); if (rc == -1) { od_error(logger, "tls", client, NULL, "error: %s, login time %" PRIu64 " us", od_io_error(&client->io), machine_time_us() - client->time_accept); return -1; } od_debug(logger, "tls", client, NULL, "ok"); return 0; } machine_tls_t *od_tls_backend(od_tls_opts_t *opts) { int rc; machine_tls_t *tls; tls = machine_tls_create(); if (tls == NULL) { return NULL; } switch (opts->tls_mode) { case OD_CONFIG_TLS_ALLOW: case OD_CONFIG_TLS_PREFER: machine_tls_set_verify(tls, "none"); break; case OD_CONFIG_TLS_REQUIRE: machine_tls_set_verify(tls, "peer"); break; default: machine_tls_set_verify(tls, "peer_strict"); break; } if (opts->tls_ca_file) { rc = machine_tls_set_ca_file(tls, opts->tls_ca_file); if (rc == -1) { machine_tls_free(tls); return NULL; } } if (opts->tls_cert_file) { rc = machine_tls_set_cert_file(tls, opts->tls_cert_file); if (rc == -1) { machine_tls_free(tls); return NULL; } } if (opts->tls_key_file) { rc = machine_tls_set_key_file(tls, opts->tls_key_file); if (rc == -1) { machine_tls_free(tls); return NULL; } } return tls; } int od_tls_backend_connect(od_server_t *server, od_logger_t *logger, od_tls_opts_t *opts) { od_debug(logger, "tls", NULL, server, "init"); /* SSL Request */ machine_msg_t *msg; msg = kiwi_fe_write_ssl_request(NULL); if (msg == NULL) { return -1; } int rc; rc = od_write(&server->io, msg); if (rc == -1) { od_error(logger, "tls", NULL, server, "write error: %s", od_io_error(&server->io)); return -1; } /* read server reply */ char type; rc = od_io_read(&server->io, &type, 1, UINT32_MAX); if (rc == -1) { od_error(logger, "tls", NULL, server, "read error: %s", od_io_error(&server->io)); return -1; } switch (type) { case 'S': /* supported */ od_debug(logger, "tls", NULL, server, "supported"); if (od_readahead_unread(&server->io.readahead) > 0) { od_error(logger, "tls", NULL, server, "extraneous data from server"); return -1; /* prevent possible buffer, protecting against CVE-2021-23222-like attacks */ } rc = mm_io_set_tls(server->io.io, server->tls, UINT32_MAX); if (rc == -1) { od_error(logger, "tls", NULL, server, "error: %s", od_io_error(&server->io)); return -1; } od_debug(logger, "tls", NULL, server, "ok"); break; case 'N': /* not supported */ if (opts->tls_mode == OD_CONFIG_TLS_ALLOW || opts->tls_mode == OD_CONFIG_TLS_PREFER) { od_debug(logger, "tls", NULL, server, "not supported, continue (%s)", od_config_tls_to_str(opts->tls_mode)); } else { od_error(logger, "tls", NULL, server, "not supported, closing"); return -1; } break; default: od_error(logger, "tls", NULL, server, "unexpected status reply"); return -1; } return 0; } odyssey-1.6.0-rc1/sources/tls_config.c000066400000000000000000000022251526064202200176560ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include od_tls_opts_t *od_tls_opts_alloc(void) { od_tls_opts_t *opts = od_malloc(sizeof(od_tls_opts_t)); if (opts == NULL) { return NULL; } memset(opts, 0, sizeof(od_tls_opts_t)); return opts; } static inline int od_tls_opts_str_eq(const char *a, const char *b) { if (a == NULL || b == NULL) { return a == b; } return strcmp(a, b) == 0; } int od_tls_opts_files_eq(const od_tls_opts_t *a, const od_tls_opts_t *b) { return od_tls_opts_str_eq(a->tls_ca_file, b->tls_ca_file) && od_tls_opts_str_eq(a->tls_key_file, b->tls_key_file) && od_tls_opts_str_eq(a->tls_cert_file, b->tls_cert_file); } od_retcode_t od_tls_opts_free(od_tls_opts_t *opts) { if (opts->tls) { od_free(opts->tls); } if (opts->tls_ca_file) { od_free(opts->tls_ca_file); } if (opts->tls_key_file) { od_free(opts->tls_key_file); } if (opts->tls_cert_file) { od_free(opts->tls_cert_file); } if (opts->tls_protocols) { od_free(opts->tls_protocols); } od_free(opts); return OK_RESPONSE; } odyssey-1.6.0-rc1/sources/tsa.c000066400000000000000000000043211526064202200163150ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include od_target_session_attrs_t od_tsa_get_effective(od_client_t *client) { od_route_t *route = client->route; od_target_session_attrs_t default_tsa = OD_TARGET_SESSION_ATTRS_UNDEF; if (client->source != NULL) { default_tsa = client->source->config->target_session_attrs; } od_target_session_attrs_t effective_tsa = OD_TARGET_SESSION_ATTRS_UNDEF; if (route->rule->target_session_attrs != OD_TARGET_SESSION_ATTRS_UNDEF) { effective_tsa = route->rule->target_session_attrs; } /* if listen config specifies tsa, it is considered as more powerfull default */ if (default_tsa != OD_TARGET_SESSION_ATTRS_UNDEF) { effective_tsa = default_tsa; } kiwi_var_t *hint_var = kiwi_vars_get( &client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS); /* XXX: TODO refactor kiwi_vars_get to avoid strcmp */ if (hint_var != NULL) { if (strncmp(hint_var->value, "read-only", hint_var->value_len) == 0) { effective_tsa = OD_TARGET_SESSION_ATTRS_RO; } else if (strncmp(hint_var->value, "read-write", hint_var->value_len) == 0) { effective_tsa = OD_TARGET_SESSION_ATTRS_RW; } else if (strncmp(hint_var->value, "prefer-standby", hint_var->value_len) == 0) { effective_tsa = OD_TARGET_SESSION_ATTRS_PREFER_STANDBY; } else if (strncmp(hint_var->value, "any", hint_var->value_len) == 0) { effective_tsa = OD_TARGET_SESSION_ATTRS_ANY; } } /* 'read-write' and 'read-only' is passed as is, 'any' or unknown == any */ if (effective_tsa == OD_TARGET_SESSION_ATTRS_UNDEF) { effective_tsa = OD_TARGET_SESSION_ATTRS_ANY; } return effective_tsa; } int od_tsa_match_rw_state(od_target_session_attrs_t attrs, int is_rw) { switch (attrs) { case OD_TARGET_SESSION_ATTRS_PREFER_STANDBY: case OD_TARGET_SESSION_ATTRS_ANY: return 1; case OD_TARGET_SESSION_ATTRS_RW: return is_rw; case OD_TARGET_SESSION_ATTRS_RO: /* this is primary, but we are forced to find ro backend */ return !is_rw; case OD_TARGET_SESSION_ATTRS_UNDEF: return 0; default: abort(); } } odyssey-1.6.0-rc1/sources/worker.c000066400000000000000000000166721526064202200170530ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PROM_FOUND #include #include #endif static OD_THREAD_LOCAL uint8_t *linear_alloc_buf = NULL; static OD_THREAD_LOCAL od_linear_alloc_t linear_alloc; static void setup_affinity(od_instance_t *instance, int wid) { const od_affinity_config_t *config = instance->config.cpu_affinity; if (config == NULL || config->mode == OD_AFFINITY_MODE_OFF) { return; } od_affinity_rule_t rule; od_affinity_rule_init(&rule); od_affinity_mode_t mode = od_affinity_resolve( config, &rule, OD_AFFINITY_ROLE_WORKER, wid); if (mode == OD_AFFINITY_MODE_OFF) { return; } #if !defined(__linux__) (void)rule; od_log(&instance->logger, "worker_init", NULL, NULL, "cpu affinity is not supported on this platform, ignoring"); return; #else cpu_set_t cpuset; CPU_ZERO(&cpuset); if (mode == OD_AFFINITY_MODE_RULES) { od_affinity_cpuset_export(&rule.cpuset, &cpuset); char buf[256]; od_affinity_cpuset_to_str(&rule.cpuset, buf, sizeof(buf)); od_log(&instance->logger, "worker_init", NULL, NULL, "pin worker %d to cpu %s", wid, buf); } else if (mode == OD_AFFINITY_MODE_AUTO) { int ncpu = sysconf(_SC_NPROCESSORS_ONLN); int cpunum = 0; if (ncpu > 1) { cpunum = 1 + wid % (ncpu - 1); } CPU_SET(cpunum, &cpuset); od_log(&instance->logger, "worker_init", NULL, NULL, "pin worker %d to cpu %d", wid, cpunum); } if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) { od_error(&instance->logger, "worker_init", NULL, NULL, "can't set worker cpu affinity: %s", strerror(errno)); } #endif } static inline void od_worker(void *arg) { od_worker_t *worker = arg; od_instance_t *instance = worker->global->instance; setup_affinity(instance, worker->id); /* thread global initialization */ od_thread_global **gl = od_thread_global_get(); od_retcode_t rc = od_thread_global_init(gl); machine_set_global_dtor(od_thread_global_free); if (rc != OK_RESPONSE) { /* TODO: set errno */ od_fatal(&instance->logger, "worker_init", NULL, NULL, "failed to init worker thread info"); return; } (*gl)->wid = worker->id; bool run = true; memset(&linear_alloc, 0, sizeof(linear_alloc)); if (instance->config.query_parsing.mode != OD_CONFIG_QUERY_PARSING_MODE_DISABLED) { size_t sz = instance->config.query_parsing.mem_limit_bytes; linear_alloc_buf = od_malloc(sz); if (linear_alloc_buf == NULL) { od_fatal( &instance->logger, "worker_init", NULL, NULL, "failed to init worker's linear allocator, errno=%d (%s)", mm_errno_get(), strerror(mm_errno_get())); return; } memset(linear_alloc_buf, 0, sz); od_linear_alloc_init(&linear_alloc, linear_alloc_buf, sz); mm_machine_atexit(od_free, (void *)linear_alloc_buf); } while (run) { uint32_t task_wait_timout_ms = 10 * 1000; machine_msg_t *msg; /* Inverse priorities of cliend routing to decrease chances of timeout */ msg = machine_channel_read_back(worker->task_channel, task_wait_timout_ms); if (msg == NULL) { /* no tasks within timeout, this is not an error */ continue; } od_msg_t msg_type; msg_type = machine_msg_type(msg); switch (msg_type) { case OD_MSG_CLIENT_NEW: { od_client_t *client; client = *(od_client_t **)machine_msg_data(msg); client->global = worker->global; /* * Allocate readahead buffer from this worker's * thread-local vrb cache. od_io_prepare() was not * called in od_system (only io->io was assigned), * so the buffer is obtained here, in the worker * thread that owns it. */ rc = od_readahead_prepare(&client->io.readahead); if (rc == -1) { od_error( &instance->logger, "worker", client, NULL, "failed to allocate readahead buffer, errno = %d (%s)", machine_errno(), strerror(machine_errno())); od_io_close(&client->io); od_client_free(client); od_routing_slot_release(worker->global); break; } /* for NULL-terminator and prefix, just in case */ char coro_name[10 + OD_ID_LEN]; od_id_write_to_string(&client->id, coro_name, 10 + OD_ID_LEN); int64_t coroutine_id; coroutine_id = machine_coroutine_create_named( od_frontend, client, coro_name); if (coroutine_id == -1) { od_error(&instance->logger, "worker", client, NULL, "failed to create coroutine"); od_io_close(&client->io); od_client_free(client); od_routing_slot_release(worker->global); break; } client->coroutine_id = coroutine_id; worker->clients_processed++; break; } case OD_MSG_STAT: { uint64_t count_coroutine = 0; uint64_t count_coroutine_cache = 0; uint64_t msg_allocated = 0; uint64_t msg_cache_count = 0; uint64_t msg_cache_gc_count = 0; uint64_t msg_cache_size = 0; machine_stat(&count_coroutine, &count_coroutine_cache, &msg_allocated, &msg_cache_count, &msg_cache_gc_count, &msg_cache_size); #ifdef PROM_FOUND od_prom_metrics_write_worker_stat( ((od_cron_t *)(worker->global->cron))->metrics, worker->id, msg_allocated, msg_cache_count, msg_cache_gc_count, msg_cache_size, count_coroutine, count_coroutine_cache, worker->clients_processed); #endif od_log(&instance->logger, "stats", NULL, NULL, "worker[%d]: msg (%" PRIu64 " allocated, %" PRIu64 " cached, %" PRIu64 " freed, %" PRIu64 " cache_size), " "coroutines (%" PRIu64 " active, %" PRIu64 " cached), clients_processed: %" PRIu64, worker->id, msg_allocated, msg_cache_count, msg_cache_gc_count, msg_cache_size, count_coroutine, count_coroutine_cache, worker->clients_processed); break; } case OD_MSG_SHUTDOWN: od_log(&instance->logger, "worker", NULL, NULL, "worker[%d]: shutdown message received", worker->id); run = false; break; default: od_assert(0); break; } machine_msg_free(msg); } od_log(&instance->logger, "worker", NULL, NULL, "worker[%d] stopped processing new connections", worker->id); } void od_worker_init(od_worker_t *worker, od_global_t *global, int id) { worker->machine = -1; worker->id = id; worker->global = global; worker->clients_processed = 0; } int od_worker_start(od_worker_t *worker) { od_instance_t *instance = worker->global->instance; worker->task_channel = machine_channel_create(); if (worker->task_channel == NULL) { od_error(&instance->logger, "worker", NULL, NULL, "failed to create task channel"); return -1; } char name[32]; od_snprintf(name, sizeof(name), "worker: %d", worker->id); worker->machine = machine_create(name, od_worker, worker); if (worker->machine == -1) { machine_channel_free(worker->task_channel); od_error(&instance->logger, "worker", NULL, NULL, "failed to start worker, errno = %d (%s)", machine_errno(), strerror(machine_errno())); return -1; } return 0; } od_linear_alloc_t *od_worker_get_local_linear_alloc(void) { return &linear_alloc; } void od_worker_shutdown(od_worker_t *worker) { machine_msg_t *msg; msg = machine_msg_create(0); machine_msg_set_type(msg, OD_MSG_SHUTDOWN); machine_channel_write(worker->task_channel, msg); } odyssey-1.6.0-rc1/sources/xplan.c000066400000000000000000001434431526064202200166610ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline kiwi_be_type_t msg_be_type(machine_msg_t *msg) { kiwi_be_type_t type = *(uint8_t *)machine_msg_data(msg); return type; } static inline kiwi_fe_type_t msg_fe_type(machine_msg_t *msg) { kiwi_fe_type_t type = *(uint8_t *)machine_msg_data(msg); return type; } static int msg_is_begin_query(machine_msg_t *msg) { char *data = machine_msg_data(msg); int size = machine_msg_size(msg); return size == sizeof(od_relay_deferred_begin_bytes) && memcmp(data, od_relay_deferred_begin_bytes, sizeof(od_relay_deferred_begin_bytes)) == 0; } static const char *plan_entry_type_str(od_xplan_entry_type_t type) { switch (type) { case OD_XPLAN_FORWARD: return "OD_XPLAN_FORWARD"; case OD_XPLAN_PARSE: return "OD_XPLAN_PARSE"; case OD_XPLAN_PARSE_SHADOW: return "OD_XPLAN_PARSE_SHADOW"; case OD_XPLAN_VIRTUAL_ERROR_RESPONSE: return "OD_XPLAN_VIRTUAL_ERROR_RESPONSE"; case OD_XPLAN_VIRTUAL_CLOSE_COMPLETE: return "OD_XPLAN_VIRTUAL_CLOSE_COMPLETE"; case OD_XPLAN_VIRTUAL_PARSE_COMPLETE: return "OD_XPLAN_VIRTUAL_PARSE_COMPLETE"; case OD_XPLAN_VIRTUAL_COMMAND_COMPLETE: return "OD_XPLAN_VIRTUAL_COMMAND_COMPLETE"; case OD_XPLAN_DEFERRED_BEGIN: return "OD_XPLAN_DEFERRED_BEGIN"; default: abort(); } } static machine_msg_t *plan_entry_backend_msg(od_xplan_entry_t *entry) { switch (entry->type) { case OD_XPLAN_FORWARD: if (entry->srvmsg != NULL) { return entry->srvmsg; } else if (entry->clmsg != NULL) { return entry->clmsg; } else { abort(); } break; case OD_XPLAN_PARSE: case OD_XPLAN_PARSE_SHADOW: if (entry->srvmsg != NULL) { return entry->srvmsg; } abort(); break; case OD_XPLAN_DEFERRED_BEGIN: return entry->clmsg; case OD_XPLAN_VIRTUAL_ERROR_RESPONSE: case OD_XPLAN_VIRTUAL_PARSE_COMPLETE: case OD_XPLAN_VIRTUAL_CLOSE_COMPLETE: case OD_XPLAN_VIRTUAL_COMMAND_COMPLETE: return NULL; default: abort(); } } static const char *plan_delta_type_str(od_xplan_delta_type_t type) { switch (type) { case OD_XPLAN_DELTA_NONE: return "OD_XPLAN_DELTA_NONE"; case OD_XPLAN_DELTA_ADD_BOTH: return "OD_XPLAN_DELTA_ADD_BOTH"; case OD_XPLAN_DELTA_ADD_CLIENT_ONLY: return "OD_XPLAN_DELTA_ADD_CLIENT_ONLY"; case OD_XPLAN_DELTA_ADD_SERVER_ONLY: return "OD_XPLAN_DELTA_ADD_SERVER_ONLY"; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY: return "OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY"; case OD_XPLAN_DELTA_REMOVE_ALL: return "OD_XPLAN_DELTA_REMOVE_ALL"; case OD_XPLAN_DELTA_REMOVE_CLIENT_ALL: return "OD_XPLAN_DELTA_REMOVE_CLIENT_ALL"; case OD_XPLAN_DELTA_ADD_PORTAL: return "OD_XPLAN_DELTA_ADD_PORTAL"; case OD_XPLAN_DELTA_REMOVE_PORTAL: return "OD_XPLAN_DELTA_REMOVE_PORTAL"; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME: return "OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME"; default: abort(); } } static int plan_delta_description(od_xplan_delta_t *delta, char *out, size_t max) { char *pos = out; char *end = out + max; pos += od_snprintf(pos, end - pos, "type=%s, client_pstmt='%s', pstmt=%s", plan_delta_type_str(delta->type), delta->client_pstmt ? delta->client_pstmt : "(null)", delta->pstmt ? delta->pstmt->name : "(null)"); if (delta->portal_name) { pos += od_snprintf(pos, end - pos, ", portal='%s'", delta->portal_name); } return pos - out; } static int plan_entry_description(od_xplan_entry_t *entry, char *out, size_t max) { char *pos = out; char *end = out + max; pos += od_snprintf(pos, end - pos, "["); pos += od_snprintf(pos, end - pos, "%s", plan_entry_type_str(entry->type)); machine_msg_t *bmsg = plan_entry_backend_msg(entry); if (bmsg != NULL) { pos += od_snprintf(pos, end - pos, ", bmsg=%c", msg_fe_type(bmsg)); } pos += od_snprintf(pos, end - pos, ", delta=("); pos += plan_delta_description(&entry->delta, pos, end - pos); pos += od_snprintf(pos, end - pos, ")]"); return pos - out; } static inline machine_msg_t *pstmt_already_exists_msg(const char *name) { char buf[OD_QRY_MAX_SZ]; int len = od_snprintf(buf, sizeof(buf), "prepared statement \"%s\" already exists", name); return kiwi_be_write_error(NULL, KIWI_DUPLICATE_PSTATEMENT, buf, len); } static inline machine_msg_t *pstmt_does_not_exists_msg(const char *name) { char buf[OD_QRY_MAX_SZ]; int len; if (name[0] != '\0') { len = od_snprintf(buf, sizeof(buf), "prepared statement \"%s\" does not exist", name); } else { len = od_snprintf(buf, sizeof(buf), "unnamed prepared statement does not exist"); } return kiwi_be_write_error(NULL, KIWI_INVALID_SQL_STATEMENT_NAME, buf, len); } static void plan_entry_destroy(void *a) { od_xplan_entry_t *entry = a; /* entry->clmsg is owned by xbuf, no free */ machine_msg_free_safe(entry->srvmsg); machine_msg_free_safe(entry->vresp); /* * no need to destroy delta: * - client name points into clmsg (owned by xbuf) * - pstmt points to the global hashmap * * exception: for OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME the * client_pstmt is an owned NUL-terminated copy (see plan_execute), * free it here */ if (entry->delta.type == OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME) { od_free((void *)entry->delta.client_pstmt); } if (entry->delta.pstmt != NULL) { od_pstmt_unref(entry->delta.pstmt); } memset(entry, 0, sizeof(od_xplan_entry_t)); } static void entry_init_internal(od_xplan_entry_t *e, od_xplan_entry_type_t type, machine_msg_t *clmsg, machine_msg_t *srvmsg, machine_msg_t *vresp, od_xplan_delta_type_t delta_type, const char *client_pstmt, od_pstmt_t *pstmt, const char *portal_name) { memset(e, 0, sizeof(od_xplan_entry_t)); e->type = type; e->clmsg = clmsg; e->srvmsg = srvmsg; e->vresp = vresp; e->delta.type = delta_type; e->delta.client_pstmt = client_pstmt; e->delta.pstmt = pstmt; e->delta.portal_name = portal_name; if (e->delta.pstmt != NULL) { od_pstmt_ref(e->delta.pstmt); } } static void entry_init_fwd(od_xplan_entry_t *e, machine_msg_t *original, machine_msg_t *rewritten, od_xplan_delta_type_t delta_type, const char *client_pstmt, od_pstmt_t *pstmt) { entry_init_internal(e, OD_XPLAN_FORWARD, original, rewritten, NULL, delta_type, client_pstmt, pstmt, NULL); } static void entry_init_fwd_no_delta(od_xplan_entry_t *e, machine_msg_t *original, machine_msg_t *rewritten) { entry_init_fwd(e, original, rewritten, OD_XPLAN_DELTA_NONE, NULL, NULL); } static void entry_init_parse(od_xplan_entry_t *e, const char *client_pstmt, od_pstmt_t *pstmt, machine_msg_t *original, machine_msg_t *rewritten) { entry_init_internal(e, OD_XPLAN_PARSE, original, rewritten, NULL, OD_XPLAN_DELTA_ADD_BOTH, client_pstmt, pstmt, NULL); } static void entry_init_parse_shadow(od_xplan_entry_t *e, od_pstmt_t *pstmt, machine_msg_t *original, machine_msg_t *inserted_parse) { entry_init_internal(e, OD_XPLAN_PARSE_SHADOW, original, inserted_parse, NULL, OD_XPLAN_DELTA_ADD_SERVER_ONLY, NULL, pstmt, NULL); } static void entry_init_virtual_error_response(od_xplan_entry_t *e, machine_msg_t *original, machine_msg_t *msg) { entry_init_internal(e, OD_XPLAN_VIRTUAL_ERROR_RESPONSE, original, NULL, msg, OD_XPLAN_DELTA_NONE, NULL, NULL, NULL); } static void entry_init_virtual_parse_complete(od_xplan_entry_t *e, const char *client_pstmt, od_pstmt_t *pstmt, machine_msg_t *original) { entry_init_internal(e, OD_XPLAN_VIRTUAL_PARSE_COMPLETE, original, NULL, NULL /* no virtual msg - const seq of bytes */, OD_XPLAN_DELTA_ADD_CLIENT_ONLY, client_pstmt, pstmt, NULL); } static void entry_init_virtual_close_complete(od_xplan_entry_t *e, const char *client_pstmt, machine_msg_t *original) { entry_init_internal(e, OD_XPLAN_VIRTUAL_CLOSE_COMPLETE, original, NULL, NULL /* no virtual msg - const seq of bytes */, OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY, client_pstmt, NULL, NULL); } static void entry_init_deferred_begin(od_xplan_entry_t *e, machine_msg_t *begin) { od_assert(msg_is_begin_query(begin)); entry_init_internal(e, OD_XPLAN_DEFERRED_BEGIN, begin, NULL, NULL, OD_XPLAN_DELTA_NONE, NULL, NULL, NULL); } /* * Forward (e.g. Bind) that also adds a portal -> pstmt mapping on client. */ static void entry_init_fwd_portal(od_xplan_entry_t *e, machine_msg_t *original, machine_msg_t *rewritten, const char *portal_name, od_pstmt_t *pstmt) { entry_init_internal(e, OD_XPLAN_FORWARD, original, rewritten, NULL, OD_XPLAN_DELTA_ADD_PORTAL, NULL, pstmt, portal_name); } /* * Virtual CommandComplete for a virtualized Execute (DEALLOCATE). * Combines multiple deltas: * - REMOVE_CLIENT_ALL for DEALLOCATE ALL * - REMOVE_CLIENT_ONLY_BY_NAME for DEALLOCATE name * * DISCARD ALL is NOT virtualized (forwarded to server), so it never * goes through this path. * * vcc_data/vcc_len is a pre-built CommandComplete wire packet, * points to a static array, not owned. */ static void entry_init_virtual_command_complete( od_xplan_entry_t *e, machine_msg_t *original, od_xplan_delta_type_t delta_type, const char *client_pstmt, const char *portal_name, const uint8_t *vcc_data, size_t vcc_len) { entry_init_internal(e, OD_XPLAN_VIRTUAL_COMMAND_COMPLETE, original, NULL, NULL, delta_type, client_pstmt, NULL, portal_name); e->vcc_data = vcc_data; e->vcc_len = vcc_len; } static int xplan_append(od_xplan_t *xp, const od_xplan_entry_t *e) { return mm_vector_append(&xp->entries, e); } static inline od_frontend_status_t xplan_append_fwd(od_xplan_t *xp, machine_msg_t *original, machine_msg_t *rewritten, od_xplan_delta_type_t delta_type, const char *client_pstmt, od_pstmt_t *pstmt) { od_xplan_entry_t e; entry_init_fwd(&e, original, rewritten, delta_type, client_pstmt, pstmt); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_fwd_no_delta(od_xplan_t *xp, machine_msg_t *original, machine_msg_t *rewritten) { od_xplan_entry_t e; entry_init_fwd_no_delta(&e, original, rewritten); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } /* * forward an Execute (or similar) with OD_XPLAN_DELTA_NONE, but attach * pstmt so that delta_apply can inspect pstmt->query_ctx after the * server replies (e.g. to clear backend_pin on UNLISTEN *). */ static inline od_frontend_status_t xplan_append_fwd_with_pstmt(od_xplan_t *xp, machine_msg_t *original, od_pstmt_t *pstmt) { od_xplan_entry_t e; entry_init_fwd(&e, original, NULL /* no rewrite */, OD_XPLAN_DELTA_NONE, NULL, pstmt); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_parse(od_xplan_t *xp, const char *client_pstmt, od_pstmt_t *pstmt, machine_msg_t *original, machine_msg_t *rewritten) { od_xplan_entry_t e; entry_init_parse(&e, client_pstmt, pstmt, original, rewritten); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_parse_shadow(od_xplan_t *xp, od_pstmt_t *pstmt, machine_msg_t *original, machine_msg_t *inserted_parse) { od_xplan_entry_t e; entry_init_parse_shadow(&e, pstmt, original, inserted_parse); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_virtual_error_response(od_xplan_t *xp, machine_msg_t *original, machine_msg_t *msg) { od_xplan_entry_t e; entry_init_virtual_error_response(&e, original, msg); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_virtual_parse_complete(od_xplan_t *xp, const char *client_pstmt, od_pstmt_t *pstmt, machine_msg_t *original) { od_xplan_entry_t e; entry_init_virtual_parse_complete(&e, client_pstmt, pstmt, original); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_virtual_close_complete(od_xplan_t *xp, const char *client_pstmt, machine_msg_t *original) { od_xplan_entry_t e; entry_init_virtual_close_complete(&e, client_pstmt, original); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_deferred_begin(od_xplan_t *xp, machine_msg_t *begin) { od_xplan_entry_t e; entry_init_deferred_begin(&e, begin); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_fwd_portal(od_xplan_t *xp, machine_msg_t *original, machine_msg_t *rewritten, const char *portal_name, od_pstmt_t *pstmt) { od_xplan_entry_t e; entry_init_fwd_portal(&e, original, rewritten, portal_name, pstmt); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } static inline od_frontend_status_t xplan_append_virtual_command_complete( od_xplan_t *xp, machine_msg_t *original, od_xplan_delta_type_t delta_type, const char *client_pstmt, const char *portal_name, const uint8_t *vcc_data, size_t vcc_len) { od_xplan_entry_t e; entry_init_virtual_command_complete(&e, original, delta_type, client_pstmt, portal_name, vcc_data, vcc_len); int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } void od_xplan_init(od_xplan_t *xp) { memset(xp, 0, sizeof(od_xplan_t)); mm_vector_init(&xp->entries, sizeof(od_xplan_entry_t), plan_entry_destroy); } void od_xplan_destroy(od_xplan_t *xp) { od_xplan_clear(xp); mm_vector_destroy(&xp->entries); } void od_xplan_clear(od_xplan_t *xp) { /* TODO: do not ignore error? */ mm_vector_clear(&xp->entries); mm_vector_shrink_to_fit(&xp->entries); } static od_pstmt_t *plan_client_get_pstmt(od_xplan_t *xp, od_client_t *client, const char *pstmt_name) { /* * searching from newest state to oldest, * this means to search from tail to head of the plan deltas * and then on commited pstmts hashmap * * should not ref the returned pstmt because * it is already referenced by client or some plan entry */ for (int i = (int)mm_vector_size(&xp->entries) - 1; i >= 0; --i) { od_xplan_entry_t *e = mm_vector_get(&xp->entries, (size_t)i); od_xplan_delta_t *delta = &e->delta; switch (delta->type) { case OD_XPLAN_DELTA_ADD_SERVER_ONLY: case OD_XPLAN_DELTA_NONE: case OD_XPLAN_DELTA_ADD_PORTAL: case OD_XPLAN_DELTA_REMOVE_PORTAL: /* non-client pstmts op */ break; case OD_XPLAN_DELTA_ADD_CLIENT_ONLY: /* fallthrough */ case OD_XPLAN_DELTA_ADD_BOTH: if (strcmp(pstmt_name, delta->client_pstmt) == 0) { /* found adding of the pstmt */ return delta->pstmt; } break; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY: if (strcmp(pstmt_name, delta->client_pstmt) == 0) { /* found removing of the pstmt */ return NULL; } break; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME: if (strcmp(pstmt_name, delta->client_pstmt) == 0) { /* found removing of the pstmt */ return NULL; } break; case OD_XPLAN_DELTA_REMOVE_ALL: case OD_XPLAN_DELTA_REMOVE_CLIENT_ALL: /* all pstmts was removed, including this */ return NULL; default: abort(); } } return od_client_get_pstmt(client, pstmt_name); } static od_pstmt_t *plan_client_get_portal(od_xplan_t *xp, od_client_t *client, const char *portal_name) { /* * searching from newest state to oldest, * this means to search from tail to head of the plan deltas * and then on commited portals hashmap * * should not ref the returned pstmt because * it is already referenced by client or some plan entry */ for (int i = (int)mm_vector_size(&xp->entries) - 1; i >= 0; --i) { od_xplan_entry_t *e = mm_vector_get(&xp->entries, (size_t)i); od_xplan_delta_t *delta = &e->delta; switch (delta->type) { case OD_XPLAN_DELTA_ADD_SERVER_ONLY: case OD_XPLAN_DELTA_NONE: case OD_XPLAN_DELTA_ADD_BOTH: case OD_XPLAN_DELTA_ADD_CLIENT_ONLY: case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY: case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME: /* non-portal op */ break; case OD_XPLAN_DELTA_ADD_PORTAL: if (strcmp(portal_name, delta->portal_name) == 0) { /* found portal -> pstmt mapping */ return delta->pstmt; } break; case OD_XPLAN_DELTA_REMOVE_PORTAL: if (strcmp(portal_name, delta->portal_name) == 0) { /* found removing of the portal */ return NULL; } break; case OD_XPLAN_DELTA_REMOVE_ALL: case OD_XPLAN_DELTA_REMOVE_CLIENT_ALL: /* all portals was removed, including this */ return NULL; default: abort(); } } return od_client_get_portal(client, portal_name); } static int plan_client_pstmt_exists(od_xplan_t *xp, od_client_t *client, const char *pstmt_name) { return plan_client_get_pstmt(xp, client, pstmt_name) != NULL; } static int plan_server_has_pstmt(od_xplan_t *xp, od_server_t *server, const od_pstmt_t *pstmt) { /* * searching from newest state to oldest, * this means to search from tail to head of the plan deltas * and then on commited pstmts hashmap */ for (int i = (int)mm_vector_size(&xp->entries) - 1; i >= 0; --i) { od_xplan_entry_t *e = mm_vector_get(&xp->entries, (size_t)i); od_xplan_delta_t *delta = &e->delta; switch (delta->type) { case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY: case OD_XPLAN_DELTA_ADD_CLIENT_ONLY: case OD_XPLAN_DELTA_NONE: case OD_XPLAN_DELTA_ADD_PORTAL: case OD_XPLAN_DELTA_REMOVE_PORTAL: case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME: case OD_XPLAN_DELTA_REMOVE_CLIENT_ALL: /* non-server pstmts op */ break; case OD_XPLAN_DELTA_ADD_SERVER_ONLY: /* fallthrough */ case OD_XPLAN_DELTA_ADD_BOTH: /* XXX: check by pointers comparison? */ if (strcmp(pstmt->name, delta->pstmt->name) == 0) { /* found adding of the pstmt */ return 1; } break; case OD_XPLAN_DELTA_REMOVE_ALL: /* all pstmts was removed, including this */ return 0; default: abort(); } } return od_server_has_pstmt(server, pstmt); } static inline od_frontend_status_t plan_predeploy_pstmt(od_xplan_t *xp, od_server_t *server, machine_msg_t *clmsg, od_pstmt_t *pstmt) { if (plan_server_has_pstmt(xp, server, pstmt)) { return OD_OK; } machine_msg_t *pmsg = od_pstmt_parse_of(pstmt); if (pmsg == NULL) { return OD_EOOM; } return xplan_append_parse_shadow(xp, pstmt, clmsg, pmsg); } static od_frontend_status_t plan_parse(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { od_client_t *client = relay->client; od_server_t *server = client->server; od_instance_t *instance = client->global->instance; od_frontend_status_t st; machine_msg_t *msg = m->msg; char *pstmt_name = od_pstmt_name_from_parse(msg); if (pstmt_name == NULL) { return OD_ECLIENT_PROTOCOL_ERROR; } od_pstmt_desc_t desc = od_pstmt_desc_from_parse(msg); if (desc.data == NULL) { return OD_ECLIENT_PROTOCOL_ERROR; } /* * special case for unnamed stmt - it should not be check for duplication * because PG allows to do smth like * Parse(""), Parse(""), Parse("") * without getting any error about pstmt already exists */ if (pstmt_name[0] != '\0' && plan_client_pstmt_exists(xp, client, pstmt_name)) { machine_msg_t *vresp = pstmt_already_exists_msg(pstmt_name); if (vresp == NULL) { return OD_EOOM; } return xplan_append_virtual_error_response(xp, msg, vresp); } od_global_pstmt_map_t *global_pstmts = od_instance_get_pstmts_map(instance); od_linear_alloc_t *arena = od_worker_get_local_linear_alloc(); od_pstmt_t *pstmt = od_pstmt_create_or_get(global_pstmts, desc, arena); if (pstmt == NULL) { return OD_EOOM; } /* we now hold the ref to pstmt */ if (plan_server_has_pstmt(xp, server, pstmt)) { /* * note: xplan entry will hold the ref */ st = xplan_append_virtual_parse_complete(xp, pstmt_name, pstmt, msg); } else { machine_msg_t *pmsg = od_pstmt_parse_of(pstmt); if (pmsg != NULL) { /* * note: xplan entry will hold the ref */ st = xplan_append_parse(xp, pstmt_name, pstmt, msg, pmsg); } else { st = OD_EOOM; } } od_pstmt_unref(pstmt); return st; } static od_frontend_status_t plan_describe(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { od_client_t *client = relay->client; od_server_t *server = client->server; machine_msg_t *msg = m->msg; char *data = machine_msg_data(msg); int size = machine_msg_size(msg); uint32_t pstmt_name_len; char *pstmt_name; int rc; kiwi_fe_describe_type_t type; rc = kiwi_be_read_describe(data, size, &pstmt_name, &pstmt_name_len, &type); if (rc == -1) { return OD_ECLIENT_PROTOCOL_ERROR; } if (type == KIWI_FE_DESCRIBE_PORTAL) { /* skip this, we only need to rewrite statement */ return xplan_append_fwd_no_delta(xp, msg, NULL /* no rewrite */); } /* * note: pstmt was returned without ref holded because * it already belongs to client or some xplan entry */ od_pstmt_t *pstmt = plan_client_get_pstmt(xp, client, pstmt_name); if (pstmt == NULL) { machine_msg_t *vresp = pstmt_does_not_exists_msg(pstmt_name); if (vresp == NULL) { return OD_EOOM; } /* plan entry will take the additional ref to pstmt */ return xplan_append_virtual_error_response(xp, msg, vresp); } /* will create xplan entry with ref if needed */ od_frontend_status_t st = plan_predeploy_pstmt(xp, server, msg, pstmt); if (st != OD_OK) { return st; } /* will create copy of name for pstmt, so no ref needed */ machine_msg_t *dmsg = od_pstmt_describe_of(pstmt); if (dmsg == NULL) { return OD_EOOM; } /* additional ref to pstmt will be created */ return xplan_append_fwd_no_delta(xp, msg, dmsg); } static od_frontend_status_t plan_close(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { /* close pstmt only on client side */ (void)relay; machine_msg_t *msg = m->msg; char *data = machine_msg_data(msg); int size = machine_msg_size(msg); char *pstmt_name = NULL; uint32_t unused; kiwi_fe_close_type_t type; if (kiwi_be_read_close(data, size, &pstmt_name, &unused, &type) != 0) { return OD_ECLIENT_PROTOCOL_ERROR; } if (type == KIWI_FE_CLOSE_PORTAL) { /* * close portal: forward Close to the server (so the server * destroys its own portal) and remove the portal -> pstmt * mapping from the client. the server replies with CloseComplete * which is proxied to the client. * * srvmsg is NULL so plan_entry_backend_msg returns clmsg (the * original message owned by xbuf) - no double free on cleanup. */ od_xplan_entry_t e; entry_init_fwd(&e, msg, NULL /* no rewrite, forward clmsg */, OD_XPLAN_DELTA_REMOVE_PORTAL, NULL, NULL); e.delta.portal_name = pstmt_name; int rc = xplan_append(xp, &e); if (rc != 0) { return OD_EOOM; } return OD_OK; } else if (type == KIWI_FE_CLOSE_PREPARED_STATEMENT) { return xplan_append_virtual_close_complete(xp, pstmt_name, msg); } /* let the pg format the correct error */ return xplan_append_fwd_no_delta(xp, msg, NULL /* no rewrite */); } static inline machine_msg_t *rewrite_bind_msg(char *data, int size, int opname_start_offset, int operator_name_len, const char *opname, int opnamelen) { machine_msg_t *msg = machine_msg_create(size - operator_name_len + opnamelen); if (msg == NULL) { return msg; } char *rewrite_data = machine_msg_data(msg); /* packet header */ memcpy(rewrite_data, data, opname_start_offset); /* prefix for opname */ od_snprintf(rewrite_data + opname_start_offset, opnamelen, "%s", opname); /* rest of msg */ memcpy(rewrite_data + opname_start_offset + opnamelen, data + opname_start_offset + operator_name_len, size - opname_start_offset - operator_name_len); /* set proper size to package */ kiwi_header_set_size((kiwi_header_t *)rewrite_data, size - operator_name_len + opnamelen); return msg; } static od_frontend_status_t plan_bind(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { od_client_t *client = relay->client; od_server_t *server = client->server; machine_msg_t *msg = m->msg; char *data = machine_msg_data(msg); int size = machine_msg_size(msg); uint32_t pstmt_name_len; char *pstmt_name; uint32_t portal_name_len; char *portal_name; int rc; rc = kiwi_be_read_bind_names(data, size, &portal_name, &portal_name_len, &pstmt_name, &pstmt_name_len); if (rc == -1) { return OD_ECLIENT_PROTOCOL_ERROR; } /* no ref to pstmt: already owned by client map or some plan entry */ od_pstmt_t *pstmt = plan_client_get_pstmt(xp, client, pstmt_name); if (pstmt == NULL) { machine_msg_t *vresp = pstmt_does_not_exists_msg(pstmt_name); if (vresp == NULL) { return OD_EOOM; } return xplan_append_virtual_error_response(xp, msg, vresp); } int pstmt_start_offset = kiwi_be_bind_opname_offset(data, size); if (pstmt_start_offset < 0) { return OD_ECLIENT_PROTOCOL_ERROR; } /* predeploy will create ref to pstmt if needed */ od_frontend_status_t st = plan_predeploy_pstmt(xp, server, msg, pstmt); if (st != OD_OK) { return st; } machine_msg_t *bmsg = rewrite_bind_msg(data, size, pstmt_start_offset, pstmt_name_len, pstmt->name, strlen(pstmt->name) + 1); if (bmsg == NULL) { return OD_EOOM; } /* * forward the Bind and add portal -> pstmt mapping on client. * * the ref on pstmt created on plan entry */ return xplan_append_fwd_portal(xp, msg, bmsg, portal_name, pstmt); } static od_frontend_status_t plan_execute(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { static const uint8_t cc_deallocate[] = { 'C', 0, 0, 0, 15, 'D', 'E', 'A', 'L', 'L', 'O', 'C', 'A', 'T', 'E', 0 }; static const uint8_t cc_deallocate_all[] = { 'C', 0, 0, 0, 19, 'D', 'E', 'A', 'L', 'L', 'O', 'C', 'A', 'T', 'E', ' ', 'A', 'L', 'L', 0 }; od_client_t *client = relay->client; od_server_t *server = client->server; od_instance_t *instance = client->global->instance; machine_msg_t *msg = m->msg; char *data = machine_msg_data(msg); int size = machine_msg_size(msg); uint32_t portal_name_len; char *portal_name; int rc = kiwi_be_read_execute(data, size, &portal_name, &portal_name_len); if (rc == -1) { return OD_ECLIENT_PROTOCOL_ERROR; } /* * no ref was created on pstmt * it is already holded by client map or some plan entry */ od_pstmt_t *pstmt = plan_client_get_portal(xp, client, portal_name); if (pstmt == NULL) { /* * portal not found - just forward, let the server * produce the appropriate ErrorResponse */ return xplan_append_fwd_no_delta(xp, msg, NULL /* no rewrite */); } /* * detect virtualizable commands via the cached query_ctx flags * (parsed once at pstmt creation time in od_pstmt_create_or_get). * this avoids re-parsing the query text on every Execute. * * DISCARD ALL is NOT virtualized: it resets much more than just * prepared statements (temp tables, sequences, listen/notify, * session variables, advisory locks) and must run on the server. * it is forwarded and REMOVE_ALL delta clears both hashmaps after * the server replies (matching simple-protocol process_discard). * * DEALLOCATE ALL / DEALLOCATE name ARE virtualized (not sent to server): * only the client hashmap is cleared; the server keeps its * odyssey_pstmt_N entries (reused on next Bind). * * UNLISTEN * and other non-virtualized commands are forwarded with * the pstmt attached (OD_XPLAN_DELTA_NONE); delta_apply inspects * pstmt->query_ctx after success to apply side effects (e.g. clearing * backend_pin on UNLISTEN *). */ const od_query_ctx_t *qctx = &pstmt->query_ctx; if (qctx->is_discard_all) { od_debug(&instance->logger, "rewrite execute", client, server, "DISCARD ALL detected via portal, invalidate caches"); return xplan_append_fwd( xp, msg, NULL, OD_XPLAN_DELTA_REMOVE_ALL, NULL, NULL /* can not to pass the pstmt - it will not be used */); } if (qctx->is_deallocate_all) { od_debug( &instance->logger, "rewrite execute", client, server, "DEALLOCATE ALL detected via portal, invalidate client caches"); return xplan_append_virtual_command_complete( xp, msg, OD_XPLAN_DELTA_REMOVE_CLIENT_ALL, NULL, NULL, cc_deallocate_all, sizeof(cc_deallocate_all)); } if (qctx->has_deallocate_name) { /* DEALLOCATE name */ od_debug(&instance->logger, "rewrite execute", client, server, "DEALLOCATE '%s' detected via portal", qctx->deallocate_name); /* * TODO: PG returns ERROR 26000 if the statement does not * exist; we always report success, same as * process_vdeallocate() in the simple path. * * deallocate_name is stored inplace in od_pstmt_t.query_ctx. * The client_pstmt delta field is passed to hashmap operations * (strcmp/strlen), so make an owned NUL-terminated copy. It is * freed in plan_entry_destroy. */ char *dealloc_name_z = od_strdup(qctx->deallocate_name); if (dealloc_name_z == NULL) { return OD_EOOM; } od_frontend_status_t st = xplan_append_virtual_command_complete( xp, msg, OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME, dealloc_name_z, NULL, cc_deallocate, sizeof(cc_deallocate)); if (st != OD_OK) { od_free(dealloc_name_z); } return st; } /* * not a virtualized command (DISCARD ALL / DEALLOCATE) — forward to * the server. pass pstmt so that delta_apply can inspect the cached * query_ctx flags (e.g. is_unlisten_all to clear backend_pin after * successful execution). */ return xplan_append_fwd_with_pstmt(xp, msg, pstmt); } static od_frontend_status_t plan_flush(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { (void)relay; return xplan_append_fwd_no_delta(xp, m->msg, NULL /* no rewrite */); } static od_frontend_status_t plan_sync(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { (void)relay; return xplan_append_fwd_no_delta(xp, m->msg, NULL /* no rewrite */); } static od_frontend_status_t plan_deferred_begin(od_relay_t *relay, od_xplan_t *xp, od_xbuf_msg_t *m) { (void)relay; return xplan_append_deferred_begin(xp, m->msg); } static od_frontend_status_t plan_simple(od_xplan_t *xp, od_relay_t *relay) { od_relay_xbuf_t *xbuf = &relay->xbuf; /* if no prepared statements must be preserved - just send all as it is from xbuf */ size_t count = mm_vector_size(&xbuf->msgs); for (size_t i = 0; i < count; ++i) { od_xbuf_msg_t *m = mm_vector_get(&xbuf->msgs, i); od_frontend_status_t status; if (i == 0 && msg_is_begin_query(m->msg)) { /* deferred begin plan entry is always at the beggining */ status = xplan_append_deferred_begin(xp, m->msg); } else { status = xplan_append_fwd_no_delta(xp, m->msg, NULL); } if (status != OD_OK) { return status; } } return OD_OK; } static od_frontend_status_t plan_with_pstmt_support(od_xplan_t *xp, od_relay_t *relay) { od_frontend_status_t status = OD_OK; od_relay_xbuf_t *xbuf = &relay->xbuf; size_t count = mm_vector_size(&xbuf->msgs); if (od_unlikely(count == 0)) { abort(); } /* handle Flush/Sync out of cycle */ --count; for (size_t i = 0; i < count; ++i) { od_xbuf_msg_t *m = mm_vector_get(&xbuf->msgs, i); kiwi_fe_type_t type = msg_fe_type(m->msg); switch (type) { case KIWI_FE_QUERY: if (od_unlikely(i != 0 || !msg_is_begin_query(m->msg))) { /* deferred begin must always be at the begginig of the plan */ abort(); } status = plan_deferred_begin(relay, xp, m); break; case KIWI_FE_PARSE: status = plan_parse(relay, xp, m); break; case KIWI_FE_DESCRIBE: status = plan_describe(relay, xp, m); break; case KIWI_FE_CLOSE: status = plan_close(relay, xp, m); break; case KIWI_FE_BIND: status = plan_bind(relay, xp, m); break; case KIWI_FE_EXECUTE: status = plan_execute(relay, xp, m); break; default: abort(); } if (status != OD_OK) { return status; } if (mm_vector_size(&xp->entries) == 0) { continue; } od_xplan_entry_t *last = mm_vector_back(&xp->entries); if (last->type == OD_XPLAN_VIRTUAL_ERROR_RESPONSE) { /* * last planned was error response * now skip all until flush/sync (which is last in xbuf) */ break; } } od_xbuf_msg_t *last = mm_vector_back(&xbuf->msgs); kiwi_fe_type_t type = msg_fe_type(last->msg); switch (type) { case KIWI_FE_FLUSH: status = plan_flush(relay, xp, last); break; case KIWI_FE_SYNC: status = plan_sync(relay, xp, last); break; default: abort(); } return status; } od_frontend_status_t od_xplan_make_from_xbuf(od_xplan_t *xp, od_relay_t *relay) { od_client_t *client = relay->client; od_server_t *server = client->server; od_relay_xbuf_t *xbuf = &relay->xbuf; od_assert(mm_vector_size(&xbuf->msgs) > 0); if (server->xproto_err) { /* * if is in xproto err state - nothing should be planned * (after error all messages is skipped until Sync) * * so, plan only one forward for the last msg - Flush/Sync */ od_xbuf_msg_t *last = mm_vector_back(&xbuf->msgs); od_assert(msg_fe_type(last->msg) == KIWI_FE_FLUSH || msg_fe_type(last->msg) == KIWI_FE_SYNC); return xplan_append_fwd_no_delta(xp, last->msg, NULL); } od_route_t *route = client->route; od_rule_t *rule = route->rule; int reserve_prepared = rule->pool->reserve_prepared_statement; if (!reserve_prepared) { return plan_simple(xp, relay); } return plan_with_pstmt_support(xp, relay); } static od_frontend_status_t delta_apply(od_xplan_delta_t *delta, od_client_t *client, od_server_t *server) { od_xplan_delta_type_t type = delta->type; /* * UNLISTEN * is forwarded (not virtualized) — the server must * actually drop the subscription. after success, clear * backend_pin, matching simple-protocol process_unlisten. */ if (delta->pstmt != NULL && delta->pstmt->query_ctx.is_unlisten_all) { client->backend_pin = 0; } if (type == OD_XPLAN_DELTA_ADD_BOTH || type == OD_XPLAN_DELTA_ADD_CLIENT_ONLY) { int rc = od_client_add_pstmt(client, delta->client_pstmt, delta->pstmt); if (rc == -1) { return OD_EOOM; } } if (type == OD_XPLAN_DELTA_ADD_BOTH || type == OD_XPLAN_DELTA_ADD_SERVER_ONLY) { int rc = od_server_add_pstmt(server, delta->pstmt); if (rc == -1) { return OD_EOOM; } } switch (type) { case OD_XPLAN_DELTA_NONE: break; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY: od_client_remove_pstmt(client, delta->client_pstmt); break; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY_BY_NAME: od_client_remove_pstmt(client, delta->client_pstmt); break; case OD_XPLAN_DELTA_ADD_PORTAL: od_client_add_portal(client, delta->portal_name, delta->pstmt); break; case OD_XPLAN_DELTA_REMOVE_PORTAL: od_client_remove_portal(client, delta->portal_name); break; case OD_XPLAN_DELTA_REMOVE_ALL: od_client_pstmts_clear(client); od_client_portals_clear(client); od_server_pstmts_clear(server); break; case OD_XPLAN_DELTA_REMOVE_CLIENT_ALL: od_client_pstmts_clear(client); od_client_portals_clear(client); break; case OD_XPLAN_DELTA_ADD_BOTH: case OD_XPLAN_DELTA_ADD_CLIENT_ONLY: case OD_XPLAN_DELTA_ADD_SERVER_ONLY: /* already handled */ break; default: abort(); } return OD_OK; } static size_t prepare_send_iovecs(od_xplan_t *xp, struct iovec *iovecs) { size_t nentries = mm_vector_size(&xp->entries); size_t written = 0; int errored = 0; for (size_t i = 0; i < nentries; ++i) { od_xplan_entry_t *e = mm_vector_get(&xp->entries, i); if (errored && i != nentries - 1) { /* when virtual error met, skip all until Flush/Sync */ continue; } if (e->type == OD_XPLAN_VIRTUAL_ERROR_RESPONSE) { errored = 1; continue; } machine_msg_t *msg = plan_entry_backend_msg(e); if (msg != NULL) { iovecs[written++] = machine_msg_iovec(msg); } } return written; } static od_frontend_status_t run_virtual_error(od_xplan_entry_t *error, od_client_t *client, uint32_t timeout_ms) { int rc = od_io_write(&client->io, error->vresp, timeout_ms); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_OK; } static od_frontend_status_t run_virtual_parse_complete(od_xplan_entry_t *pc, od_client_t *client, uint32_t timeout_ms) { (void)pc; static const uint8_t bytes[] = { KIWI_BE_PARSE_COMPLETE, 0, 0, 0, 4 }; od_assert(sizeof(bytes) == 5); size_t unused; int rc = od_io_write_raw(&client->io, bytes, sizeof(bytes), &unused, timeout_ms, 0); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_OK; } static od_frontend_status_t run_virtual_close_complete(od_xplan_entry_t *cc, od_client_t *client, uint32_t timeout_ms) { (void)cc; static const uint8_t bytes[] = { KIWI_BE_CLOSE_COMPLETE, 0, 0, 0, 4 }; od_assert(sizeof(bytes) == 5); size_t unused; int rc = od_io_write_raw(&client->io, bytes, sizeof(bytes), &unused, timeout_ms, 0); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_OK; } static od_frontend_status_t run_virtual_command_complete(od_xplan_entry_t *cc, od_client_t *client, uint32_t timeout_ms) { od_assert(cc->vcc_data != NULL && cc->vcc_len > 0); size_t unused; int rc = od_io_write_raw(&client->io, cc->vcc_data, cc->vcc_len, &unused, timeout_ms, 0); if (rc != 0) { return OD_ECLIENT_WRITE; } return OD_OK; } static od_frontend_status_t run_parse_shadow(od_xplan_entry_t *ps, od_client_t *client, od_server_t *server, uint32_t timeout_ms) { (void)ps; od_instance_t *instance = client->global->instance; machine_msg_t *msg = od_read(&server->io, timeout_ms, OD_READ_BE); if (msg == NULL) { return OD_ESERVER_READ; } od_frontend_status_t status; kiwi_be_type_t type = msg_be_type(msg); switch (type) { case KIWI_BE_PARSE_COMPLETE: /* drop response - client should not see PC from shadow parse */ status = OD_OK; break; case KIWI_BE_ERROR_RESPONSE: /* * i think this ErrorResponse is nearly impossible on shadow parsing * but lets handle it, just in case */ od_backend_error(server, "main", machine_msg_data(msg), machine_msg_size(msg)); od_error(&instance->logger, "main", client, server, "run_parse_shadow: ErrorResponse met"); status = OD_ESERVER_READ; break; default: od_error( &instance->logger, "main", client, server, "run_parse_shadow: unexpected msg type from server '%c'", type); status = OD_ESERVER_READ; break; } machine_msg_free(msg); return status; } static od_frontend_status_t run_deferred_begin(od_xplan_entry_t *ps, od_client_t *client, od_server_t *server, uint32_t timeout_ms) { (void)ps; /* * read until RFQ * do not resend CommandComplete and RFQ to client */ int rc; int done = 0; machine_msg_t *msg = NULL; while (!done) { msg = od_read(&server->io, timeout_ms, OD_READ_BE); if (msg == NULL) { return OD_ESERVER_READ; } char *data = machine_msg_data(msg); int size = machine_msg_size(msg); kiwi_be_type_t type = *data; od_instance_t *instance = client->global->instance; if (instance->config.log_debug) { if (type == KIWI_BE_COMMAND_COMPLETE) { const char *command_tag = data + sizeof(kiwi_header_t); od_debug(&instance->logger, "main", client, server, "%s - %s", kiwi_be_type_to_string(type), command_tag); } else { od_debug(&instance->logger, "main", client, server, "%s", kiwi_be_type_to_string(type)); } } switch (type) { case KIWI_BE_READY_FOR_QUERY: done = 1; /* fallthrough */ case KIWI_BE_COMMAND_COMPLETE: machine_msg_free(msg); break; case KIWI_BE_PARAMETER_STATUS: rc = od_backend_update_parameter(server, "main", data, size, 0); if (rc == -1) { machine_msg_free(msg); od_gerror("main", client, server, "can't update parameter"); return OD_ESERVER_READ; } /* fallthrough */ default: rc = od_write2(&client->io, msg, timeout_ms); if (rc != 0) { return OD_ECLIENT_READ; } break; } } return OD_OK; } static od_frontend_status_t run_parse(od_xplan_entry_t *ps, od_client_t *client, od_server_t *server, uint32_t timeout_ms) { (void)ps; (void)client; return od_stream_server_exact_completes("main", server, 1 /* one complete for parse */, NULL, NULL, timeout_ms); } typedef struct { od_client_t *client; od_server_t *server; od_xplan_t *xp; size_t idx; } forward_batch_response_arg_t; static od_frontend_status_t forward_apply_delta_cb(kiwi_be_type_t type, void *a) { forward_batch_response_arg_t *arg = a; od_client_t *client = arg->client; od_server_t *server = arg->server; od_instance_t *instance = client->global->instance; od_rule_t *rule = client->route->rule; od_xplan_t *xp = arg->xp; od_xplan_entry_t *entry = mm_vector_get(&xp->entries, arg->idx); arg->idx++; od_frontend_status_t status = OD_OK; int apply_delta = 0; if (!server->xproto_err) { status = delta_apply(&entry->delta, client, server); apply_delta = 1; } if (instance->config.log_debug || rule->log_debug) { char buf[256]; plan_entry_description(entry, buf, sizeof(buf)); od_debug( &instance->logger, "main", client, server, "%s entry finished, response type = %c, apply_delta = %d", buf, type, apply_delta); } return status; } static od_frontend_status_t run_forward_batch(od_xplan_t *xp, size_t begin, size_t end, /* [begin; end) */ od_client_t *client, od_server_t *server, uint32_t timeout_ms) { od_assert(end > begin); int has_sync = 0; #ifndef NDEBUG for (size_t i = begin; i < end; ++i) { od_xplan_entry_t *e = mm_vector_get(&xp->entries, i); od_assert(e->type == OD_XPLAN_FORWARD); } #endif if (end == mm_vector_size(&xp->entries)) { od_xplan_entry_t *last = mm_vector_get(&xp->entries, end - 1); kiwi_fe_type_t last_type = msg_fe_type(last->clmsg); if (last_type == KIWI_FE_FLUSH) { /* * Flush will not produce any responce from backend * but we count RFQ as response for Sync */ --end; } else if (last_type == KIWI_FE_SYNC) { has_sync = 1; } } od_frontend_status_t status = OD_OK; forward_batch_response_arg_t arg; arg.client = client; arg.server = server; arg.idx = begin; arg.xp = xp; while (arg.idx < end && !server->xproto_err) { size_t left = end - arg.idx; status = od_stream_server_exact_completes("main", server, left, forward_apply_delta_cb, &arg, timeout_ms); if (status == OD_COPY_IN_RECEIVED) { machine_msg_t *add = od_relay_get_copy_additional(&client->relay); status = od_stream_copy_to_server( "main", client, server, add, timeout_ms); od_relay_set_copy_additional(&client->relay, NULL); int rc = od_io_write_flush(&server->io, timeout_ms); if (rc != 0) { return OD_ESERVER_WRITE; } if (has_sync) { /* * in case some msgs like: * Bind Execute(COPY FROM) Sync(ignored) CopyData CopyData * * we must not expect the response for this sync */ --end; has_sync = 0; } } if (status != OD_OK) { return status; } } if (status != OD_OK) { return status; } /* * if Sync was included into batch and exact_completes stopped early * on ErrorResponse, RFQ is still pending and must be drained */ if (has_sync && server->xproto_err) { status = od_stream_server_sync("main", server, timeout_ms); } return status; } static od_frontend_status_t run_plan_impl(od_xplan_t *xp, od_relay_t *relay, size_t max_responses, uint32_t timeout_ms) { /* TODO: use this to check correctness */ (void)max_responses; od_client_t *client = relay->client; od_instance_t *instance = client->global->instance; od_rule_t *rule = client->route->rule; od_server_t *server = client->server; od_frontend_status_t status = OD_OK; /* * almost most of plan entries leads to exactly one 'response': * Parse -> ErrorResponse | ParseComplete * Bind -> ErrorResponse | BindComplete * Close -> ErrorResponse | CloseComplete (responded virtually) * Execute -> DataRow* + ErrorResponse | CommandComplete | EmptyQueryResponse | PortalSuspended * Describe -> ParameterDescription* + RowDescription | NoData * Sync -> ReadyForQuery * * individually: * Flush -> * Deffered BEGIN -> read until RFQ, resend only async messages to client * no response awaiting from server */ od_stat_query_start(&server->stats_state); if (!server->xproto_mode) { /* this is first run of xproto buf after last sync */ od_server_sync_request(server, 1); } /* * we are now running the server in xproto mode * this flag will be set to 0 on ReadyForQuery */ server->xproto_mode = 1; size_t count = mm_vector_size(&xp->entries); od_assert(count > 0); int forward_begin = -1; for (size_t i = 0; i < count; ++i) { if (server->xproto_err && i != count - 1) { /* if error is met, skip all until Flush/Sync */ continue; } od_xplan_entry_t *entry = mm_vector_get(&xp->entries, i); if (entry->type == OD_XPLAN_FORWARD) { if (forward_begin == -1) { forward_begin = (int)i; } continue; } if (forward_begin != -1) { /* need to handle forwards range */ status = run_forward_batch(xp, (size_t)forward_begin, i, client, server, timeout_ms); if (status != OD_OK) { return status; } forward_begin = -1; } switch (entry->type) { case OD_XPLAN_DEFERRED_BEGIN: status = run_deferred_begin(entry, client, server, timeout_ms); break; case OD_XPLAN_PARSE: status = run_parse(entry, client, server, timeout_ms); break; case OD_XPLAN_PARSE_SHADOW: status = run_parse_shadow(entry, client, server, timeout_ms); break; case OD_XPLAN_VIRTUAL_ERROR_RESPONSE: server->xproto_err = 1; status = run_virtual_error(entry, client, timeout_ms); break; case OD_XPLAN_VIRTUAL_PARSE_COMPLETE: status = run_virtual_parse_complete(entry, client, timeout_ms); break; case OD_XPLAN_VIRTUAL_CLOSE_COMPLETE: status = run_virtual_close_complete(entry, client, timeout_ms); break; case OD_XPLAN_VIRTUAL_COMMAND_COMPLETE: status = run_virtual_command_complete(entry, client, timeout_ms); break; case OD_XPLAN_FORWARD: /* fallthrough */ default: abort(); } if (status == OD_OK && !server->xproto_err) { status = delta_apply(&entry->delta, client, server); } if (instance->config.log_debug || rule->log_debug) { char buf[256]; plan_entry_description(entry, buf, sizeof(buf)); od_debug(&instance->logger, "main", client, server, "%s entry finished with status %s", buf, od_frontend_status_to_str(status)); } if (status != OD_OK) { return status; } } if (forward_begin != -1) { /* need to handle tail forwards range */ status = run_forward_batch(xp, (size_t)forward_begin, count, client, server, timeout_ms); } return status; } static od_frontend_status_t send_buf_and_run_plan(od_xplan_t *xp, od_relay_t *relay, struct iovec *iovecs, size_t niovecs, uint32_t timeout_ms) { od_client_t *client = relay->client; od_server_t *server = client->server; int rc = od_io_writev(&server->io, iovecs, niovecs, timeout_ms, 0); if (rc == -1) { return OD_ESERVER_WRITE; } /* * use niovecs because it shows real number of how much responses we are awaiting * (some might have been skipped after virtual error response) */ return run_plan_impl(xp, relay, niovecs, timeout_ms); } static inline void log_plan_debug(od_instance_t *instance, od_xplan_t *xp, od_client_t *client) { char *msg = od_malloc(1024); if (msg == NULL) { return; } char *end = msg + 1024; char *pos = msg; size_t count = mm_vector_size(&xp->entries); pos += od_snprintf(pos, end - pos, "("); for (size_t i = 0; i < count; ++i) { od_xplan_entry_t *entry = mm_vector_get(&xp->entries, i); pos += plan_entry_description(entry, pos, end - pos); } pos += od_snprintf(pos, end - pos, ")"); od_debug(&instance->logger, "main", client, client->server, "xplan: %s", msg); od_free(msg); } static inline void log_srv_send_buf(od_instance_t *instance, od_client_t *client, const struct iovec *iovecs, size_t niovecs) { char *msg = od_malloc(1024); if (msg == NULL) { return; } char *end = msg + 1024; char *pos = msg; pos += od_snprintf(pos, end - pos, "("); for (size_t i = 0; i < niovecs; ++i) { struct iovec vec = iovecs[i]; pos += od_snprintf(pos, end - pos, "{msg=%c, len=%lu}", *(const char *)vec.iov_base, vec.iov_len); } pos += od_snprintf(pos, end - pos, ")"); od_debug(&instance->logger, "main", client, client->server, "server buf: %s", msg); od_free(msg); } static inline void log_xbuf(od_instance_t *instance, od_client_t *client, od_relay_xbuf_t *xbuf) { char *msg = od_malloc(1024); if (msg == NULL) { return; } char *end = msg + 1024; char *pos = msg; pos += od_snprintf(pos, end - pos, "("); od_xbuf_msg_t *m = mm_vector_get(&xbuf->msgs, 0); pos += od_snprintf(pos, end - pos, "%c", msg_fe_type(m->msg)); size_t count = mm_vector_size(&xbuf->msgs); for (size_t i = 1; i < count; ++i) { m = mm_vector_get(&xbuf->msgs, i); pos += od_snprintf(pos, end - pos, ", %c", msg_fe_type(m->msg)); } pos += od_snprintf(pos, end - pos, ")"); od_debug(&instance->logger, "main", client, client->server, "xbuf: %s", msg); od_free(msg); } od_frontend_status_t od_xplan_run(od_xplan_t *xp, od_relay_t *relay, uint32_t timeout_ms) { od_client_t *client = relay->client; od_rule_t *rule = client->route->rule; od_instance_t *instance = client->global->instance; size_t count = mm_vector_size(&xp->entries); od_assert(count > 0); size_t niovecs = count; struct iovec *iovecs = od_malloc(sizeof(struct iovec) * niovecs); if (iovecs == NULL) { return OD_EOOM; } size_t written = prepare_send_iovecs(xp, iovecs); if (instance->config.log_debug || rule->log_debug) { log_xbuf(instance, client, &relay->xbuf); log_plan_debug(instance, xp, client); log_srv_send_buf(instance, client, iovecs, written); } od_frontend_status_t status = send_buf_and_run_plan(xp, relay, iovecs, written, timeout_ms); od_free(iovecs); return status; } odyssey-1.6.0-rc1/test/000077500000000000000000000000001526064202200146565ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/async-notice/000077500000000000000000000000001526064202200172525ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/async-notice/Dockerfile000066400000000000000000000004571526064202200212520ustar00rootroot00000000000000FROM golang:1.25.0 RUN apt-get update -y && apt-get install -y postgresql-client postgresql-contrib RUN mkdir -p /test WORKDIR /test COPY ./test/async-notice/entrypoint.sh . COPY ./test/async-notice/go.mod . COPY ./test/async-notice/main_test.go . RUN go mod tidy ENTRYPOINT ["/test/entrypoint.sh"]odyssey-1.6.0-rc1/test/async-notice/docker-compose.yml000066400000000000000000000013241526064202200227070ustar00rootroot00000000000000services: primary: image: postgres:18 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - prom_exporter_net healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey volumes: - ./test.conf:/etc/odyssey/odyssey.conf networks: - prom_exporter_net depends_on: - primary tester: build: context: ../../ # odyssey root dir dockerfile: ./test/async-notice/Dockerfile networks: - prom_exporter_net depends_on: - odyssey networks: prom_exporter_net: driver: bridge odyssey-1.6.0-rc1/test/async-notice/entrypoint.sh000077500000000000000000000005101526064202200220200ustar00rootroot00000000000000#!/bin/sh set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 0.1 done until pg_isready -h odyssey -p 6432 -U postgres -d postgres; do echo "Wait for odyssey..." sleep 0.1 done DATABASE_URL='postgres://postgres@odyssey:6432/postgres?sslmode=disable' go test -v . odyssey-1.6.0-rc1/test/async-notice/go.mod000066400000000000000000000003671526064202200203660ustar00rootroot00000000000000module asynctest go 1.25.0 require github.com/jackc/pgx/v5 v5.9.2 require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect golang.org/x/text v0.36.0 // indirect ) odyssey-1.6.0-rc1/test/async-notice/go.sum000066400000000000000000000043731526064202200204140ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= odyssey-1.6.0-rc1/test/async-notice/main_test.go000066400000000000000000000034671526064202200215760ustar00rootroot00000000000000package asynctest import ( "context" "fmt" "os" "sync" "testing" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" ) func TestWaitForNotificationImmediate(t *testing.T) { // export DATABASE_URL='postgres://rkhapov@localhost:5432/postgres?sslmode=disable' dsn := os.Getenv("DATABASE_URL") if dsn == "" { t.Error("DATABASE_URL is not set") t.FailNow() } ctx := context.TODO() listenerConn, err := pgx.Connect(ctx, dsn) if err != nil { t.Fatalf("connect listener: %v", err) } defer listenerConn.Close(ctx) senderConn, err := pgx.Connect(ctx, dsn) if err != nil { t.Fatalf("connect sender: %v", err) } defer senderConn.Close(ctx) channel := fmt.Sprintf("test_notify_%d", time.Now().UnixNano()) _, err = listenerConn.Exec(ctx, "listen "+pgx.Identifier{channel}.Sanitize()) if err != nil { t.Fatalf("listen: %v", err) } receivedCh := make(chan *pgconn.Notification, 1) errCh := make(chan error, 1) readyWg := &sync.WaitGroup{} readyWg.Add(1) go func() { waitCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() readyWg.Done() n, err := listenerConn.WaitForNotification(waitCtx) if err != nil { errCh <- err return } receivedCh <- n }() readyWg.Wait() payload := "hello" _, err = senderConn.Exec(ctx, "select pg_notify($1, $2)", channel, payload) if err != nil { t.Fatalf("notify: %v", err) } select { case err := <-errCh: t.Fatalf("wait failed: %v", err) case n := <-receivedCh: if n.Channel != channel { t.Fatalf("unexpected channel: got %q want %q", n.Channel, channel) } if n.Payload != payload { t.Fatalf("unexpected payload: got %q want %q", n.Payload, payload) } case <-time.After(2 * time.Second): t.Fatal("timeout waiting for notification") } } func TestMain(m *testing.M) { m.Run() } odyssey-1.6.0-rc1/test/async-notice/test.conf000066400000000000000000000011061526064202200210760ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_session yes log_query no log_stats no daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "primary" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "session" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/build-test/000077500000000000000000000000001526064202200167325ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/build-test/Dockerfile.debian000066400000000000000000000015731526064202200221530ustar00rootroot00000000000000ARG codename=bookworm FROM debian:${codename} ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid-until RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ curl lsb-release ca-certificates gnupg RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ sudo \ git \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ libsystemd-dev \ cmake \ bison flex \ devscripts debhelper COPY . /build_dir WORKDIR /build_dir ENTRYPOINT [ "bash", "-c", "dpkg-buildpackage -us -uc -b" ] odyssey-1.6.0-rc1/test/build-test/Dockerfile.fedora000066400000000000000000000002461526064202200221650ustar00rootroot00000000000000FROM fedora:40 RUN dnf install -y cmake gcc make git openssl-devel systemd-devel bison flex COPY . /workdir WORKDIR /workdir RUN make clean RUN make build_release odyssey-1.6.0-rc1/test/build-test/Dockerfile.oraclelinux000066400000000000000000000003471526064202200232540ustar00rootroot00000000000000ARG version=8 FROM oraclelinux:${version} RUN dnf install -y make gcc cmake git openssl-devel systemd-devel bison flex COPY . /build_dir WORKDIR /build_dir RUN make clean ENTRYPOINT [ "bash", "-c", "make $ODYSSEY_BUILD_TYPE" ] odyssey-1.6.0-rc1/test/build-test/Dockerfile.ubuntu000066400000000000000000000016301526064202200222450ustar00rootroot00000000000000ARG codename=focal FROM ubuntu:${codename} ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ curl lsb-release ca-certificates gnupg RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ sudo \ git \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ libsystemd-dev \ cmake \ bison flex \ devscripts debhelper COPY . /build_dir WORKDIR /build_dir ENTRYPOINT [ "bash", "-c", "dpkg-buildpackage -us -uc -b" ] odyssey-1.6.0-rc1/test/drivers/000077500000000000000000000000001526064202200163345ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/jdbc/000077500000000000000000000000001526064202200172365ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/jdbc/docker-compose.yml000066400000000000000000000015661526064202200227030ustar00rootroot00000000000000services: primary: image: postgres:18 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - jdb_regress_net healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey environment: - PG_HOST=primary - LOG_CONFIG=yes - USER_AUTH_TYPE=none networks: - jdb_regress_net depends_on: - primary regress_test: build: context: https://github.com/pg-sharding/jdbc-spqr.git environment: - PG_HOST=odyssey - PG_PORT=6432 - PG_USER=postgres - PG_DATABASE=postgres networks: - jdb_regress_net command: - --skip-sharding-setup depends_on: - odyssey networks: jdb_regress_net: driver: bridge odyssey-1.6.0-rc1/test/drivers/userver/000077500000000000000000000000001526064202200200275ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/userver/CMakeLists.txt000066400000000000000000000006571526064202200225770ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(userver_notes CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(userver REQUIRED COMPONENTS core postgresql) add_executable(${PROJECT_NAME} src/main.cpp src/create_note.cpp src/get_note.cpp src/ping.cpp src/portal_fill.cpp src/portal_scan.cpp ) target_link_libraries(${PROJECT_NAME} userver::core userver::postgresql ) odyssey-1.6.0-rc1/test/drivers/userver/Dockerfile.userver-test000066400000000000000000000004611526064202200244710ustar00rootroot00000000000000FROM ghcr.io/userver-framework/ubuntu-22.04-userver-pg:latest WORKDIR /app COPY . /app RUN cmake -S /app -B /app/build \ && cmake --build /app/build -j"$(nproc)" RUN pip3 install \ pytest \ pytest-asyncio \ aiohttp RUN chmod +x /app/scripts/run-all.sh CMD ["/app/scripts/run-all.sh"] odyssey-1.6.0-rc1/test/drivers/userver/configs/000077500000000000000000000000001526064202200214575ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/userver/configs/config.yaml000066400000000000000000000023011526064202200236040ustar00rootroot00000000000000components_manager: task_processors: main-task-processor: worker_threads: 4 fs-task-processor: worker_threads: 2 default_task_processor: main-task-processor components: server: listener: port: 8080 task_processor: main-task-processor logging: fs-task-processor: fs-task-processor loggers: default: file_path: "@stdout" level: warning dns-client: fs-task-processor: fs-task-processor testsuite-support: load-enabled: true handler-ping: path: /ping method: GET task_processor: main-task-processor handler-create-note: path: /v1/notes method: POST task_processor: main-task-processor handler-get-note: path: /v1/notes/{id} method: GET task_processor: main-task-processor postgres-db: dbconnection: postgres://postgres@odyssey:6432/postgres blocking_task_processor: fs-task-processor handler-portal-fill: path: /v1/portal-fill method: POST task_processor: main-task-processor handler-portal-scan: path: /v1/portal-scan method: GET task_processor: main-task-processor odyssey-1.6.0-rc1/test/drivers/userver/configs/dynamic_config_fallback.json000066400000000000000000000004061526064202200271420ustar00rootroot00000000000000{ "POSTGRES_CONNECTION_SETTINGS": { "max_pool_size": 16, "min_pool_size": 4 }, "USERVER_LOG_REQUEST": true, "USERVER_TASK_PROCESSOR_QOS": {}, "USERVER_CORO_POOL": { "initial_size": 1000, "max_size": 4000, "stack_size": 262144 } } odyssey-1.6.0-rc1/test/drivers/userver/docker-compose.yml000066400000000000000000000017041526064202200234660ustar00rootroot00000000000000services: primary: image: postgres:18 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - userver_net healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey volumes: - ./odyssey.conf:/etc/odyssey/odyssey.conf:ro networks: - userver_net depends_on: - primary userver_test: build: context: . dockerfile: Dockerfile.userver-test environment: - PG_HOST=odyssey - PG_PORT=6432 - PG_USER=postgres - PG_DATABASE=postgres networks: - userver_net cap_add: - SYS_PTRACE depends_on: primary: condition: service_healthy odyssey: condition: service_started security_opt: - seccomp:userfaultfd.json networks: userver_net: driver: bridge odyssey-1.6.0-rc1/test/drivers/userver/odyssey.conf000066400000000000000000000025331526064202200224000ustar00rootroot00000000000000daemonize no log_format "tskv\ttimestamp=%t\tpid=%p\tunixtime=%n\tms=%e\tlevel=%l\tcid=%i\tsid=%s\tdb=%d\tuser=%u\tctx=%c\tcluster=mdbqpglgm5m6cqg1kvq0\thostname=sus\torigin=odyssey\tremote_addr=%h\tremote_port=%r\tmsg=%M\n" log_to_stdout yes log_debug no log_query no log_config yes log_session yes log_stats no stats_interval 60 readahead 4096 cache_msg_gc_size 12288 nodelay yes keepalive 15 keepalive_keep_interval 3 keepalive_probes 3 client_max 32000 resolvers 1 workers 1 listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "local" { type "local" } storage "pg_server" { type "remote" host "primary" port 5432 tls "disable" } database "postgres" { user "postgres" { authentication "none" storage "pg_server" pool "session" pool_size 140 application_name_add_host yes pool_timeout 1000 pool_ttl 20 pool_reserve_prepared_statement no pool_discard yes pool_cancel yes pool_rollback yes client_max 32000 client_fwd_error yes quantiles "1,0.9999,0.999,0.99,0.95,0.9,0.75,0.5" } }odyssey-1.6.0-rc1/test/drivers/userver/scripts/000077500000000000000000000000001526064202200215165ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/userver/scripts/run-all.sh000066400000000000000000000017041526064202200234260ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail echo "Waiting for odyssey at ${PG_HOST}:${PG_PORT} ..." for i in $(seq 1 60); do if bash -c "echo > /dev/tcp/${PG_HOST}/${PG_PORT}" 2>/dev/null; then break fi sleep 1 done echo "Starting userver service..." /app/build/userver_notes --config /app/configs/config.yaml & SERVICE_PID=$! cleanup() { if kill -0 "${SERVICE_PID}" 2>/dev/null; then kill "${SERVICE_PID}" 2>/dev/null || true wait "${SERVICE_PID}" 2>/dev/null || true fi } trap cleanup EXIT echo "Waiting for HTTP service..." READY=0 for i in $(seq 1 20); do if ! kill -0 "${SERVICE_PID}" 2>/dev/null; then echo "userver service exited prematurely" wait "${SERVICE_PID}" || true exit 1 fi if curl -sf http://127.0.0.1:8080/ping >/dev/null; then READY=1 break fi sleep 1 done if [ "${READY}" -ne 1 ]; then echo "Service did not become ready in time" exit 1 fi echo "Running pytest..." pytest /app/tests -v odyssey-1.6.0-rc1/test/drivers/userver/src/000077500000000000000000000000001526064202200206165ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/userver/src/create_note.cpp000066400000000000000000000030501526064202200236100ustar00rootroot00000000000000#include "create_note.hpp" #include #include #include #include #include namespace notes { CreateNote::CreateNote(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context) : HttpHandlerBase(config, context), pg_cluster_( context.FindComponent("postgres-db") .GetCluster()) {} std::string CreateNote::HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext&) const { const auto json = userver::formats::json::FromString(request.RequestBody()); const auto text = json["text"].As(); pg_cluster_->Execute( userver::storages::postgres::ClusterHostType::kMaster, "CREATE TABLE IF NOT EXISTS notes (" "id BIGSERIAL PRIMARY KEY, " "text TEXT NOT NULL)"); auto result = pg_cluster_->Execute( userver::storages::postgres::ClusterHostType::kMaster, "INSERT INTO notes(text) VALUES($1) RETURNING id", text); const auto id = result.AsSingleRow(); request.GetHttpResponse().SetContentType("application/json"); userver::formats::json::ValueBuilder response; response["id"] = id; response["text"] = text; return userver::formats::json::ToString(response.ExtractValue()); } } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/create_note.hpp000066400000000000000000000012701526064202200236170ustar00rootroot00000000000000#pragma once #include #include namespace notes { class CreateNote final : public userver::server::handlers::HttpHandlerBase { public: static constexpr std::string_view kName = "handler-create-note"; CreateNote(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context); std::string HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext& context) const override; private: userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/get_note.cpp000066400000000000000000000026561526064202200231370ustar00rootroot00000000000000#include "get_note.hpp" #include #include #include #include #include namespace notes { GetNote::GetNote(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context) : HttpHandlerBase(config, context), pg_cluster_( context.FindComponent("postgres-db") .GetCluster()) {} std::string GetNote::HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext&) const { const auto id = std::stoll(request.GetPathArg("id")); auto result = pg_cluster_->Execute( userver::storages::postgres::ClusterHostType::kMaster, "SELECT id, text FROM notes WHERE id = $1", id); if (result.IsEmpty()) { throw userver::server::handlers::ResourceNotFound( userver::server::handlers::ExternalBody{"note not found"}); } request.GetHttpResponse().SetContentType("application/json"); userver::formats::json::ValueBuilder response; response["id"] = result[0]["id"].As(); response["text"] = result[0]["text"].As(); return userver::formats::json::ToString(response.ExtractValue()); } } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/get_note.hpp000066400000000000000000000012541526064202200231350ustar00rootroot00000000000000#pragma once #include #include namespace notes { class GetNote final : public userver::server::handlers::HttpHandlerBase { public: static constexpr std::string_view kName = "handler-get-note"; GetNote(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context); std::string HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext& context) const override; private: userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/main.cpp000066400000000000000000000016631526064202200222540ustar00rootroot00000000000000#include #include #include #include #include #include "create_note.hpp" #include "get_note.hpp" #include "ping.hpp" #include "portal_fill.hpp" #include "portal_scan.hpp" int main(int argc, char* argv[]) { auto component_list = userver::components::MinimalServerComponentList() .Append() .Append() .Append("postgres-db") .Append() .Append() .Append() .Append() .Append(); return userver::utils::DaemonMain(argc, argv, component_list); } odyssey-1.6.0-rc1/test/drivers/userver/src/ping.cpp000066400000000000000000000003361526064202200222610ustar00rootroot00000000000000#include "ping.hpp" namespace notes { std::string Ping::HandleRequestThrow( const userver::server::http::HttpRequest&, userver::server::request::RequestContext&) const { return "OK"; } } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/ping.hpp000066400000000000000000000007121526064202200222640ustar00rootroot00000000000000#pragma once #include namespace notes { class Ping final : public userver::server::handlers::HttpHandlerBase { public: static constexpr std::string_view kName = "handler-ping"; using HttpHandlerBase::HttpHandlerBase; std::string HandleRequestThrow( const userver::server::http::HttpRequest&, userver::server::request::RequestContext&) const override; }; } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/portal_fill.cpp000066400000000000000000000041271526064202200236350ustar00rootroot00000000000000#include "portal_fill.hpp" #include #include #include #include namespace notes { namespace pg = userver::storages::postgres; namespace { std::string MakePayload(int id, int payload_size) { std::string prefix = "note-" + std::to_string(id) + "|"; if (static_cast(prefix.size()) >= payload_size) { return prefix; } std::string result = prefix; result.append(payload_size - static_cast(prefix.size()), 'x'); return result; } } // namespace PortalFill::PortalFill(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context) : HttpHandlerBase(config, context), pg_cluster_( context.FindComponent("postgres-db") .GetCluster()) {} std::string PortalFill::HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext&) const { const auto count = request.GetArg("count").empty() ? 1000 : std::stoi(request.GetArg("count")); const auto payload_size = request.GetArg("payload_size").empty() ? 256 : std::stoi(request.GetArg("payload_size")); pg_cluster_->Execute( pg::ClusterHostType::kMaster, "CREATE TABLE IF NOT EXISTS notes (" "id BIGSERIAL PRIMARY KEY, " "text TEXT NOT NULL)"); pg_cluster_->Execute( pg::ClusterHostType::kMaster, "TRUNCATE TABLE notes RESTART IDENTITY"); for (int i = 1; i <= count; ++i) { pg_cluster_->Execute( pg::ClusterHostType::kMaster, "INSERT INTO notes(text) VALUES($1)", MakePayload(i, payload_size)); } request.GetHttpResponse().SetContentType("application/json"); userver::formats::json::ValueBuilder response; response["inserted"] = count; response["payload_size"] = payload_size; return userver::formats::json::ToString(response.ExtractValue()); } } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/portal_fill.hpp000066400000000000000000000012701526064202200236360ustar00rootroot00000000000000#pragma once #include #include namespace notes { class PortalFill final : public userver::server::handlers::HttpHandlerBase { public: static constexpr std::string_view kName = "handler-portal-fill"; PortalFill(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context); std::string HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext& context) const override; private: userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/portal_scan.cpp000066400000000000000000000106601526064202200236320ustar00rootroot00000000000000#include "portal_scan.hpp" #include #include #include #include #include #include namespace notes { namespace pg = userver::storages::postgres; PortalScan::PortalScan(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context) : HttpHandlerBase(config, context), pg_cluster_( context.FindComponent("postgres-db") .GetCluster()) {} std::string PortalScan::HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext&) const { const auto chunk_size = request.GetArg("chunk_size").empty() ? 100 : std::stoi(request.GetArg("chunk_size")); const auto last_id = request.GetArg("last_id").empty() ? 0 : std::stoll(request.GetArg("last_id")); const auto min_payload_size = request.GetArg("min_payload_size").empty() ? 0 : std::stoi(request.GetArg("min_payload_size")); auto trx = pg_cluster_->Begin( pg::ClusterHostType::kMaster, pg::TransactionOptions(pg::Transaction::RO)); auto portal = trx.MakePortal( "SELECT id, text FROM notes WHERE id > $1 ORDER BY id", last_id); std::size_t total_rows = 0; std::int64_t expected_id = last_id + 1; std::int64_t fetched_last_id = last_id; std::size_t chunk_index = 0; request.GetHttpResponse().SetContentType("application/json"); while (portal) { auto res = portal.Fetch(chunk_size); if (res.IsEmpty()) { break; } ++chunk_index; for (std::size_t i = 0; i < res.Size(); ++i) { const auto id = res[i]["id"].As(); const auto text = res[i]["text"].As(); const auto expected_prefix = "note-" + std::to_string(id) + "|"; if (id != expected_id) { trx.Rollback(); userver::formats::json::ValueBuilder error; error["ok"] = false; error["error"] = "unexpected id"; error["chunk_index"] = static_cast(chunk_index); error["row_index_in_chunk"] = static_cast(i); error["expected_id"] = expected_id; error["actual_id"] = id; return userver::formats::json::ToString(error.ExtractValue()); } if (text.rfind(expected_prefix, 0) != 0) { trx.Rollback(); userver::formats::json::ValueBuilder error; error["ok"] = false; error["error"] = "unexpected text prefix"; error["chunk_index"] = static_cast(chunk_index); error["row_index_in_chunk"] = static_cast(i); error["id"] = id; error["expected_prefix"] = expected_prefix; error["actual_text_prefix"] = text.substr(0, std::min(text.size(), 64)); return userver::formats::json::ToString(error.ExtractValue()); } if (min_payload_size > 0 && static_cast(text.size()) < min_payload_size) { trx.Rollback(); userver::formats::json::ValueBuilder error; error["ok"] = false; error["error"] = "payload too short"; error["chunk_index"] = static_cast(chunk_index); error["row_index_in_chunk"] = static_cast(i); error["id"] = id; error["actual_size"] = static_cast(text.size()); error["expected_min_size"] = min_payload_size; return userver::formats::json::ToString(error.ExtractValue()); } ++expected_id; fetched_last_id = id; ++total_rows; } } trx.Commit(); userver::formats::json::ValueBuilder response; response["ok"] = true; response["rows"] = static_cast(total_rows); response["last_id"] = fetched_last_id; response["chunk_size"] = chunk_size; response["chunks"] = static_cast(chunk_index); return userver::formats::json::ToString(response.ExtractValue()); } } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/src/portal_scan.hpp000066400000000000000000000012701526064202200236340ustar00rootroot00000000000000#pragma once #include #include namespace notes { class PortalScan final : public userver::server::handlers::HttpHandlerBase { public: static constexpr std::string_view kName = "handler-portal-scan"; PortalScan(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context); std::string HandleRequestThrow( const userver::server::http::HttpRequest& request, userver::server::request::RequestContext& context) const override; private: userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace notes odyssey-1.6.0-rc1/test/drivers/userver/tests/000077500000000000000000000000001526064202200211715ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/drivers/userver/tests/conftest.py000066400000000000000000000003231526064202200233660ustar00rootroot00000000000000import aiohttp import pytest_asyncio @pytest_asyncio.fixture async def service_client(): async with aiohttp.ClientSession( base_url='http://127.0.0.1:8080', ) as session: yield session odyssey-1.6.0-rc1/test/drivers/userver/tests/test_notes.py000066400000000000000000000011731526064202200237340ustar00rootroot00000000000000import pytest @pytest.mark.asyncio async def test_create_and_get_note(service_client): async with service_client.post( '/v1/notes', json={'text': 'hello via odyssey'}, ) as response: assert response.status == 200 created = await response.json() assert 'id' in created assert created['text'] == 'hello via odyssey' note_id = created['id'] async with service_client.get(f'/v1/notes/{note_id}') as response: assert response.status == 200 fetched = await response.json() assert fetched == { 'id': note_id, 'text': 'hello via odyssey', } odyssey-1.6.0-rc1/test/drivers/userver/tests/test_portal_scan.py000066400000000000000000000026531526064202200251150ustar00rootroot00000000000000import pytest @pytest.mark.asyncio @pytest.mark.parametrize('chunk_size', [1, 7, 64, 128, 1024]) async def test_portal_scan_validates_rows(service_client, chunk_size): rows_count = 50000 async with service_client.post( f'/v1/portal-fill?count={rows_count}', ) as response: assert response.status == 200 filled = await response.json(content_type=None) assert filled['inserted'] == rows_count async with service_client.get( f'/v1/portal-scan?chunk_size={chunk_size}&last_id=0', ) as response: assert response.status == 200 scanned = await response.json(content_type=None) assert scanned['ok'] is True assert scanned['rows'] == rows_count assert scanned['last_id'] == rows_count assert scanned['chunk_size'] == chunk_size @pytest.mark.asyncio async def test_portal_scan_validates_tail(service_client): rows_count = 50000 last_id = 3456 expected_rows = rows_count - last_id async with service_client.post( f'/v1/portal-fill?count={rows_count}', ) as response: assert response.status == 200 async with service_client.get( f'/v1/portal-scan?chunk_size=113&last_id={last_id}', ) as response: assert response.status == 200 scanned = await response.json(content_type=None) assert scanned['ok'] is True assert scanned['rows'] == expected_rows assert scanned['last_id'] == rows_count odyssey-1.6.0-rc1/test/drivers/userver/userfaultfd.json000066400000000000000000000000661526064202200232500ustar00rootroot00000000000000{"names": ["userfaultfd"], "action": "SCMP_ACT_ALLOW"}odyssey-1.6.0-rc1/test/functional/000077500000000000000000000000001526064202200170205ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/Dockerfile000066400000000000000000000164351526064202200210230ustar00rootroot00000000000000FROM ubuntu:noble AS odyssey-build-env ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ cmake \ clang \ libsystemd-dev \ bison flex \ libpthread-stubs0-dev libclang-rt-dev FROM odyssey-build-env AS odyssey-build ARG odyssey_build_type ARG odyssey_cc RUN mkdir build_dir WORKDIR /build_dir COPY . . ENV CC=${odyssey_cc} RUN make clean RUN make ${odyssey_build_type} FROM golang:1.25-alpine AS golang-tests-builder RUN mkdir -p /ody_integration_test RUN mkdir -p /prep_stmts RUN mkdir -p /config-validation RUN mkdir -p /external_auth COPY ./test/functional/tests/ody_integration_test /ody_integration_test COPY ./test/functional/tests/prep_stmts /prep_stmts COPY ./test/functional/tests/config-validation /config-validation COPY ./test/functional/tests/external_auth /external_auth WORKDIR /ody_integration_test RUN go mod download && cd pkg && CGO_ENABLED=0 go build -o ody_integration_test WORKDIR /prep_stmts RUN go mod download && cd pkg && CGO_ENABLED=0 go build -o pstmts-test WORKDIR /config-validation RUN go mod download && cd pkg && go build -o config-validation WORKDIR /external_auth RUN go mod download && cd pkg && go build -o external-auth-agent FROM ubuntu:noble AS dotnet-tests-builder RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ software-properties-common RUN add-apt-repository ppa:dotnet/backports RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ dotnet-sdk-10.0 dotnet-runtime-10.0 COPY ./test/functional/tests/npgsql_compat /npgsql_compat RUN cd /npgsql_compat && dotnet build FROM ubuntu:noble AS postgres-setup RUN apt update && apt install -y ca-certificates curl gnupg RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN install -d /usr/share/postgresql-common/pgdg RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | gpg --dearmor -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg RUN echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" \ > /etc/apt/sources.list.d/pgdg.list RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ sudo \ postgresql-18 \ postgresql-common COPY ./test/functional/bin/setup /usr/bin/setup COPY ./test/functional/bin/teardown /usr/bin/teardown RUN /usr/bin/setup RUN /usr/bin/teardown FROM postgres-setup AS test-runner RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated ca-certificates curl software-properties-common RUN install -m 0755 -d /etc/apt/keyrings RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc RUN chmod a+r /etc/apt/keyrings/docker.asc RUN echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null RUN add-apt-repository ppa:dotnet/backports RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ build-essential \ openssl \ cmake \ clang \ gdb \ strace \ ltrace \ bpftrace \ ldap-utils \ python3 \ lsof \ postgresql-16 \ postgresql-common \ python3 python3-pip python3-venv \ sudo \ git \ vim \ xxd \ docker-ce docker-ce-cli containerd.io \ dotnet-sdk-10.0 dotnet-runtime-10.0 \ openssh-server \ llvm libpthread-stubs0-dev libclang-rt-dev # Taken from - https://docs.docker.com/engine/examples/running_ssh_service/#environment-variables RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd RUN echo 'PermitRootLogin yes' > /etc/ssh/sshd_config.d/10.myconf.conf # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE="in users profile" RUN echo "export VISIBLE=now" >> /etc/profile # 22 for ssh server. 7777 for gdb server. EXPOSE 22 7777 RUN useradd -ms /bin/bash debugger RUN echo 'debugger:pwd' | chpasswd RUN mkdir /tmp/odyssey RUN mkdir /tests COPY ./test/functional/tests/pg/pg_hba-test.conf /etc/postgresql/16/main/pg_hba.conf COPY ./test/functional/odyssey.conf /etc/odyssey/odyssey.conf COPY ./test/functional/bin/ody-restart /usr/bin/ody-restart COPY ./test/functional/bin/ody-start /usr/bin/ody-start COPY ./test/functional/bin/ody-start2 /usr/bin/ody-start2 COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop COPY ./test/functional/bin/start-pg /usr/bin/start-pg COPY ./test/functional/tests /tests WORKDIR /tests COPY ./sources/machinarium/gdb/machinarium-gdb.py /gdb.py COPY --from=golang-tests-builder /ody_integration_test/pkg/ody_integration_test ./ody_integration_test/ody_integration_test COPY --from=golang-tests-builder /prep_stmts/pkg/pstmts-test ./prep_stmts/pstmts-test COPY --from=golang-tests-builder /config-validation/pkg/config-validation ./config-validation/config-validation COPY --from=golang-tests-builder /external_auth/pkg/external-auth-agent ./external_auth/external-auth-agent COPY --from=dotnet-tests-builder /npgsql_compat/src/NpgsqlOdysseyScram.Console/bin/Debug/net10.0/* ./npgsql_compat/ COPY --from=odyssey-build /build_dir/build/sources/odyssey /usr/bin/odyssey COPY --from=odyssey-build /build_dir/build/sources/odyssey_test /usr/bin/odyssey_test COPY --from=odyssey-build /build_dir/build/sources/machinarium /machinarium COPY --from=odyssey-build /build_dir /build_dir COPY ./test/functional/requirements.txt . RUN python3 -m venv /opt/tests/venv ENV PATH="/opt/tests/venv/bin:$PATH" RUN pip install -Ur requirements.txt --index-url=https://pypi-mirror.gitverse.ru/simple/ COPY ./test/functional/test_functional.py . FROM test-runner AS functional-entrypoint WORKDIR /tests ENTRYPOINT ["pytest", "-s", "-vv", "-x"] FROM test-runner AS dev-env ENTRYPOINT ["/usr/sbin/sshd", "-D"] odyssey-1.6.0-rc1/test/functional/bin/000077500000000000000000000000001526064202200175705ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/bin/ody-reload000077500000000000000000000000721526064202200215540ustar00rootroot00000000000000#!/bin/bash old_pid=$(pgrep odyssey) kill -s HUP $old_pid odyssey-1.6.0-rc1/test/functional/bin/ody-restart000077500000000000000000000001461526064202200217740ustar00rootroot00000000000000#!/bin/bash #old_pid=$(pgrep odyssey) /usr/bin/odyssey /etc/odyssey/odyssey.conf #kill -USR2 $old_pid odyssey-1.6.0-rc1/test/functional/bin/ody-start000077500000000000000000000000671526064202200214470ustar00rootroot00000000000000#!/bin/bash /usr/bin/odyssey /etc/odyssey/odyssey.conf odyssey-1.6.0-rc1/test/functional/bin/ody-start2000077500000000000000000000005551526064202200215330ustar00rootroot00000000000000#!/bin/bash /usr/bin/odyssey "$1" MAX_RETRIES=50 COUNT=0 until pg_isready -h localhost -p 6432 -U "$2" -d "$2"; do if [ $COUNT -ge $MAX_RETRIES ]; then echo "ERROR: Timeout waiting for Odyssey after $MAX_RETRIES seconds." exit 1 fi echo "Wait for odyssey... ($COUNT/$MAX_RETRIES)" sleep 0.1 COUNT=$((COUNT+1)) done echo "Odyssey is ready!"odyssey-1.6.0-rc1/test/functional/bin/ody-stop000077500000000000000000000060661526064202200213040ustar00rootroot00000000000000#!/usr/bin/env python3 import os import time import signal import subprocess import sys import re ASAN_LOG_PREFIX = '/asan-output.log' TSAN_LOG_PREFIX = '/tsan-output.log' def get_sanitizer_log(pid, prefix): filename = f'{prefix}.{pid}' if not os.path.exists(filename) or os.path.getsize(filename) == 0: return b'' with open(filename, 'rb') as logfile: print(f'Found non-empty {filename} log:') data = logfile.read() print(data.decode('utf-8')) return data def asan_log_is_valid(pid): # ASAN_OPITONS was set to this log_prefix in docker-compose.yml logdata = get_sanitizer_log(pid, ASAN_LOG_PREFIX) warning_count = len(re.findall(rb'==\d+==WARNING', logdata)) error_count = len(re.findall(rb'==\d+==ERROR', logdata)) print(f'Found {error_count} errors and {warning_count} warnings in log') return error_count == 0 and warning_count <= 1 def tsan_log_is_valid(pid): # TSAN_OPITONS was set to this log_prefix in docker-compose.yml logdata = get_sanitizer_log(pid, TSAN_LOG_PREFIX) return len(logdata) == 0 from datetime import datetime def get_odyssey_pids(): print(subprocess.check_output( 'ps aux | grep odyssey', shell=True).decode('utf-8')) try: return list(map(int, subprocess.check_output(['pgrep', 'odyssey']).split())) except subprocess.CalledProcessError: # non-zero exit code means that there is no odyssey pids return [] def terminate_gracefully(pid, timeout): try: os.kill(pid, signal.SIGTERM) except ProcessLookupError: print(f'Process {pid} already finished or doesnt ever existed') return True print(f'{datetime.now()} Waiting {timeout} seconds to { pid} finish after SIGTERM...') start = time.time() finished = False while time.time() - start < timeout: try: output = subprocess.check_output(['ps', '-p', str(pid)], ) print(f'{datetime.now()} {output.decode('utf-8')}') if 'odyssey'.encode('utf-8') not in output: finished = True break except subprocess.CalledProcessError: # non-zero code means there is no process with that pid finished = True break time.sleep(0.5) print() return finished def main(): timeout = 5 if len(sys.argv) > 1: pids = [int(sys.argv[1])] else: pids = get_odyssey_pids() print('Odyssey pids to stop:', ', '.join(list(map(str, pids)))) for pid in pids: if not terminate_gracefully(pid, timeout): print(f'Process {pid} didnt finish within {timeout} seconds') print(subprocess.check_output(['gdb', '-p', str(pid), '--batch', '-ex', 't a a bt']).decode('utf-8')) print(subprocess.check_output(['gdb', '-p', str(pid), '--batch', '-ex', 'source /gdb.py', '-ex', 'mmcoro all bt']).decode('utf-8')) exit(1) if not asan_log_is_valid(pid) or not tsan_log_is_valid(pid): exit(1) exit(0) if __name__ == "__main__": main() odyssey-1.6.0-rc1/test/functional/bin/setup000077500000000000000000000242621526064202200206640ustar00rootroot00000000000000#!/bin/bash set -ex service postgresql stop || true export PG_LOG=/var/log/postgresql/postgresql-16-main.log export SETUP_LOG=/var/log/setup.log touch $SETUP_LOG rm -fr /var/lib/postgresql/18/main/ rm -fr /var/lib/postgresql/18/repl # create primary sudo -u postgres /usr/lib/postgresql/18/bin/initdb -D /var/lib/postgresql/18/main/ cat > /var/lib/postgresql/18/main/pg_hba.conf <<-EOF local scram_db postgres trust host scram_db postgres 127.0.0.1/32 trust local scram_db scram_user scram-sha-256 host scram_db scram_user 127.0.0.1/32 scram-sha-256 local scram_db frontend_and_backend_auth_scram_password scram-sha-256 host scram_db frontend_and_backend_auth_scram_password 127.0.0.1/32 scram-sha-256 EOF cat >> /var/lib/postgresql/18/main/pg_hba.conf <<-EOF host ldap_db user1 127.0.0.1/32 trust host ldap_db1 ldap_readonly 127.0.0.1/32 password host ldap_db2 ldap_rw 127.0.0.1/32 md5 host auth_query_db auth_query_user_scram_sha_256 127.0.0.1/32 trust host auth_query_db auth_query_user_md5 127.0.0.1/32 trust host auth_query_db auth_query_user_null_password 127.0.0.1/32 trust local all postgres trust host postgres user1 127.0.0.1/32 trust host db1 user1 127.0.0.1/32 trust host all postgres 127.0.0.1/32 trust host all user_allow 127.0.0.1/32 trust host all user_reject 127.0.0.1/32 trust host replication postgres 127.0.0.1/32 trust host replication postgres ::1/128 trust host tsa_db user_ro 127.0.0.1/32 trust host tsa_db user_rw 127.0.0.1/32 trust host spqr-console spqr-console 127.0.0.1/32 trust host xproto_db xproto 127.0.0.1/32 trust host addr_db all 127.0.0.1/32 password host all all ::1/128 trust EOF sed -i 's/max_connections = 100/max_connections = 2000/g' /var/lib/postgresql/18/main/postgresql.conf sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/main/ start psql -h localhost -p 5432 -U postgres -c "table pg_hba_file_rules ;" # Create databases for database_name in db scram_db ldap_db auth_query_db db1 hba_db tsa_db group_db addr_db xproto_db "spqr-console"; do sudo -u postgres createdb $database_name >> "$SETUP_LOG" 2>&1 || { echo "ERROR: 'createdb $database_name' failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } done # pgbench initialization mkdir /var/cores sudo sysctl -w kernel.core_pattern=/var/cores/core.%p.%e pgbench -i -h localhost -p 5432 -U postgres postgres # Create users psql -h localhost -p 5432 -U postgres -c "set password_encryption TO 'md5'; create role group1; create role group2;create role group3;create role group4; create user group_checker; create user group_user1 password 'password1'; create user group_user2; create user group_user3; create user group_user4; create user group_user5; create user group_user6;create user group_user7; create user group_checker1; create user group_checker2;" -d group_db >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "set password_encryption = 'scram-sha-256'; create user scram_user password 'scram_user_password';" -d scram_db >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } psql -h localhost -p 5432 -U postgres -c "set password_encryption = 'scram-sha-256'; create user frontend_and_backend_auth_scram_password password 'scram_user_password';" -d scram_db >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "create role user1 with login;create role user_ro with login;create role user_rw with login;" -d ldap_db >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "set password_encryption TO 'md5'; create user auth_query_user_md5 with password 'passwd'" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "create role auth_query_user_null_password login password null" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: null-password user creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "CREATE ROLE zz" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "set password_encryption = 'scram-sha-256'; create user auth_query_user_scram_sha_256 with password 'passwd'" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "GRANT ALL ON DATABASE db1 TO user1" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "alter user user1 with connection limit 1000" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U user1 -d db1 -c "CREATE SCHEMA sh1" >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "create user user_allow password 'correct_password'; create user user_reject password 'correct_password'; create user user_unknown password 'correct_password';" >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "create user user_addr_correct password 'correct_password'; create user user_addr_incorrect password 'correct_password'; create user user_addr_default password 'correct_password'; create user user_addr_empty password 'correct_password'; create user user_addr_hostname_localhost password 'correct_password';" >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "create role xproto SUPERUSER LOGIN" -d xproto_db >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create users psql -h localhost -p 5432 -U postgres -c "create user \"spqr-console\";" >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } for i in `seq 0 9` do # Create tables psql -h localhost -p 5432 -U user1 -d db1 -c "CREATE TABLE sh1.foo$i (i int)" >> $SETUP_LOG 2>&1 || { echo "ERROR: tables creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } done # Create tables psql -h localhost -p 5432 -U postgres -d db -c "CREATE TABLE copy_test(c1 VARCHAR(50), c2 VARCHAR(50), c3 VARCHAR(50))" >> $SETUP_LOG 2>&1 || { echo "ERROR: tables creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Create databases for ldap_storage_credentials for database_name in ldap_db1 ldap_db2; do sudo -u postgres createdb $database_name >> "$SETUP_LOG" 2>&1 || { echo "ERROR: 'createdb $database_name' failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } done # Create users for ldap_storage_credentials psql -h localhost -p 5432 -U postgres -c "create user ldap_readonly with password 'ldap_pass_readonly'" -d ldap_db1 >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } psql -h localhost -p 5432 -U postgres -c "set password_encryption TO 'md5'; create user ldap_rw with password 'ldap_pass_rw'" -d ldap_db2 >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } # Grant access for ldap_storage_credentials psql -h localhost -p 5432 -U postgres -c "GRANT ALL ON DATABASE ldap_db1 TO ldap_readonly" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } psql -h localhost -p 5432 -U postgres -c "GRANT ALL ON DATABASE ldap_db2 TO ldap_rw" -d postgres >> $SETUP_LOG 2>&1 || { echo "ERROR: users creation failed, examine the log" cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } psql -h localhost -p 5432 -U "postgres" -c 'GRANT ALL ON SCHEMA public TO "spqr-console"' -d "spqr-console" >> $SETUP_LOG 2>&1 || { echo 'ERROR: GRANT ALL ON SCHEMA public TO "spqr-console"' cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } psql -h localhost -p 5432 -U postgres -c 'CREATE DATABASE broken_collation' -d postgres >> $SETUP_LOG 2>&1 || { echo 'ERROR: CREATE DATABASE broken_collation' cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } psql -h localhost -p 5432 -U postgres -c "UPDATE pg_database SET datcollversion = 'bogus-version' WHERE datname = 'broken_collation'" -d postgres >> $SETUP_LOG 2>&1 || { echo 'ERROR: UPDATE pg_database SET datcollversion = 'bogus-version' WHERE datname = 'broken_collation'' cat "$SETUP_LOG" cat "$PG_LOG" exit 1 } sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/main/ stop odyssey-1.6.0-rc1/test/functional/bin/start-pg000077500000000000000000000010541526064202200212570ustar00rootroot00000000000000#!/bin/bash set -ex sudo rm -rf /var/lib/postgresql/18/repl/ # Run pg master and replica service postgresql stop || true sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/main/ -l /var/lib/postgresql/18/main/log.txt start if [ "$1" = "-r" ]; then sudo -u postgres /usr/bin/pg_basebackup -D /var/lib/postgresql/18/repl --checkpoint=fast -R -Fp -Xs -P -h localhost -p 5432 sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/repl/ -l /var/lib/postgresql/18/repl/log.txt -o '-p 5433' start fi odyssey-1.6.0-rc1/test/functional/bin/teardown000077500000000000000000000003461526064202200213440ustar00rootroot00000000000000#!/bin/bash # Try to stop postgresql sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/main/ stop || true sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/repl/ stop || true odyssey-1.6.0-rc1/test/functional/docker-compose.yml000066400000000000000000000024231526064202200224560ustar00rootroot00000000000000services: odyssey: extra_hosts: - "ip4-localhost:127.0.0.1" ulimits: core: # sudo sysctl -w kernel.core_pattern=/var/cores/core-%e-%p-%t soft: -1 hard: -1 privileged: true init: true build: dockerfile: ./test/functional/Dockerfile context: ../../ # odyssey root dir target: "${ODYSSEY_TEST_TARGET:-functional-entrypoint}" args: odyssey_build_type: "${ODYSSEY_FUNCTIONAL_BUILD_TYPE:-build_release}" odyssey_cc: "${ODYSSEY_CC:-gcc}" command: ["-k", "${ODYSSEY_FUNCTIONAL_TESTS_SELECTOR:-}"] environment: CMAKE_BUILD_TYPE: "${CMAKE_BUILD_TYPE:-Debug}" ASAN_OPTIONS: "log_path=/asan-output.log fast_unwind_on_malloc=0" # abort_on_error=1 TSAN_OPTIONS: "log_path=/tsan-output.log" volumes: - /var/run/docker.sock:/var/run/docker.sock - /var/cores:/var/cores ports: - "2222:22" - "7777:7777" networks: od_net: ipv4_address: 192.168.233.15 openldapr: image: "osixia/openldap:1.5.0" networks: od_net: ipv4_address: 192.168.233.16 networks: od_net: name: odyssey_od_net driver: bridge ipam: driver: default config: - subnet: 192.168.233.0/24 # - gateway: 192.168.233.1 odyssey-1.6.0-rc1/test/functional/odyssey.conf000066400000000000000000000015011526064202200213630ustar00rootroot00000000000000 storage "postgres_server" { type "remote" host "localhost" port 5432 } database default { user default { authentication "none" storage "postgres_server" # storage_db "db" # storage_user "user" # storage_password "password" pool "session" client_fwd_error yes } } unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_debug no log_config no log_session no log_query no log_stats yes daemonize yes locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes stats_interval 60 pid_file "/var/run/odyssey.pid" listen { host "*" port 6432 } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/functional/requirements.txt000066400000000000000000000000071526064202200223010ustar00rootroot00000000000000pytest odyssey-1.6.0-rc1/test/functional/test_functional.py000066400000000000000000000026541526064202200226020ustar00rootroot00000000000000import pytest import os import subprocess START_PG_PATH = '/usr/bin/start-pg' TEARDOWN_PG_PATH = '/usr/bin/teardown' TESTS_DIR_PATH = '/tests' RUNNER_SCRIPT_FILE = 'runner.sh' def get_test_folders(): folders = os.listdir(TESTS_DIR_PATH) for f in folders: if f.startswith('__') or f.startswith('.'): continue if not os.path.isdir(f): continue yield os.path.join(TESTS_DIR_PATH, f) def clear_log_file(): try: os.remove('/var/log/odyssey.log') except OSError: pass @pytest.fixture(scope="session", autouse=True) def prepare_postgres(): result = subprocess.run([START_PG_PATH, '-r'], timeout=2 * 60) if result.returncode != 0: pytest.exit( f"Failed to prepare environment, code {result.returncode}\n", returncode=result.returncode, ) yield subprocess.check_output([TEARDOWN_PG_PATH], timeout=2 * 60) @pytest.mark.parametrize('folder', list(get_test_folders())) def test_odyssey_functional(folder): clear_log_file() runner = os.path.join(folder, RUNNER_SCRIPT_FILE) process = subprocess.run(['/bin/bash', runner]) retcode = process.returncode if retcode != 0: pytest.fail( f"Process {runner} exited with code {retcode}\n" f"STDOUT:\n{process.stdout}\n" f"STDERR:\n{process.stderr}\n" ) if __name__ == '__main__': pytest.main() odyssey-1.6.0-rc1/test/functional/tests/000077500000000000000000000000001526064202200201625ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/auth_query/000077500000000000000000000000001526064202200223505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/auth_query/config.conf000066400000000000000000000026351526064202200244720ustar00rootroot00000000000000storage "postgres_server" { type "remote" # invalid host to check it should be ignored in auth query host "127.0.0.1:5438,127.0.0.1:5432,127.0.0.1:5435" } database "postgres" { user "postgres" { authentication "none" pool_routing "internal" storage "postgres_server" pool "session" pool_size 5 pool_timeout 2000 } } database "auth_query_db" { user "auth_query_user_scram_sha_256" { authentication "scram-sha-256" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage "postgres_server" pool "session" } user "auth_query_user_md5" { authentication "md5" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage "postgres_server" pool "session" } user "auth_query_user_null_password" { authentication "md5" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage "postgres_server" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session no log_stats no log_query no listen { host "127.0.0.1" port 6432 target_session_attrs "read-write" } odyssey-1.6.0-rc1/test/functional/tests/auth_query/runner.sh000077500000000000000000000035371526064202200242300ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/auth_query/config.conf sleep 1 odyssey_pid=$(cat /var/run/odyssey.pid) check_null_password_rejected() { if PGPASSWORD=passwd PGCONNECT_TIMEOUT=5 psql -h localhost -p 6432 \ -U auth_query_user_null_password -c "SELECT 1" auth_query_db; then echo "ERROR: auth_query accepted a user with a NULL password" cat /var/log/odyssey.log exit 1 fi if ! kill -0 "$odyssey_pid"; then echo "ERROR: Odyssey crashed after auth_query returned a NULL password" for i in /asan-output*; do if [ -f "$i" ]; then cat "$i" fi done cat /var/log/odyssey.log cat /var/log/postgresql/postgresql-16-main.log exit 1 fi } # The first attempt populates the negative cache entry; the second reuses it. check_null_password_rejected check_null_password_rejected timeout 25s pgbench 'host=localhost port=6432 user=auth_query_user_scram_sha_256 dbname=auth_query_db password=passwd' -f /tests/auth_query/select.sql -T 21 --connect --no-vacuum -j2 -c2 --progress 1 || { echo "ERROR: failed backend auth with correct password" sleep 1 for i in /asan-output*; do cat $i done cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } #PGPASSWORD=passwd psql -h localhost -p 6432 -U auth_query_user_scram_sha_256 -c "SELECT 1" auth_query_db >/dev/null 2>&1 || { #PGPASSWORD=passwd psql -h localhost -p 6432 -U auth_query_user_md5 -c "SELECT 1" auth_query_db >/dev/null 2>&1 || { timeout 25s pgbench 'host=localhost port=6432 user=auth_query_user_md5 dbname=auth_query_db password=passwd' -f /tests/auth_query/select.sql -T 21 --connect --no-vacuum -j2 -c2 --progress 1 || { echo "ERROR: failed backend auth with correct password" sleep 1 for i in /asan-output*; do cat $i done cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/auth_query/select.sql000066400000000000000000000000121526064202200243410ustar00rootroot00000000000000select 42;odyssey-1.6.0-rc1/test/functional/tests/cancel-leak/000077500000000000000000000000001526064202200223215ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/cancel-leak/cancel-leak.conf000066400000000000000000000014201526064202200253240ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "[127.0.0.1]:6433" } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_size 20 pool_cancel yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } } daemonize yes pid_file "/var/run/odyssey.pid" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats yes log_query no stats_interval 1 workers 2 resolvers 1 client_max_routing 16 listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/cancel-leak/evil_proxy.py000066400000000000000000000070441526064202200251000ustar00rootroot00000000000000#!/usr/bin/env python3 """ Evil TCP proxy: forwards normal PG connections transparently, but hangs cancel request connections (never closes the client socket). This simulates a network condition where the backend processes the cancel but the TCP connection never delivers EOF to Odyssey. Usage: evil_proxy.py """ import socket import struct import sys import threading import os import signal cancel_count = 0 cancel_lock = threading.Lock() def forward(src, dst, label): try: while True: data = src.recv(65536) if not data: break dst.sendall(data) except Exception: pass try: dst.shutdown(socket.SHUT_WR) except Exception: pass def handle_client(client_sock, pg_host, pg_port): global cancel_count try: # Peek at the first 8 bytes to detect cancel request header = b'' while len(header) < 8: chunk = client_sock.recv(8 - len(header), socket.MSG_PEEK) if not chunk: client_sock.close() return header += chunk msg_len = struct.unpack('!I', header[0:4])[0] code = struct.unpack('!I', header[4:8])[0] is_cancel = (msg_len == 16 and code == 80877102) # Connect to real PG pg_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) pg_sock.connect((pg_host, pg_port)) if is_cancel: # Read the full 16-byte cancel message and forward it cancel_msg = client_sock.recv(16) pg_sock.sendall(cancel_msg) pg_sock.close() with cancel_lock: cancel_count += 1 n = cancel_count print(f"[PROXY] cancel #{n}: forwarded to PG, now hanging client socket...", flush=True) # Hang: keep client_sock open forever, never close it. # This simulates the condition where Odyssey's od_read() # never gets EOF. event = threading.Event() event.wait() # blocks forever else: # Normal connection: bidirectional proxy t1 = threading.Thread(target=forward, args=(client_sock, pg_sock, "c->s"), daemon=True) t2 = threading.Thread(target=forward, args=(pg_sock, client_sock, "s->c"), daemon=True) t1.start() t2.start() t1.join() t2.join() client_sock.close() pg_sock.close() except Exception as e: print(f"[PROXY] error: {e}", flush=True) try: client_sock.close() except Exception: pass def main(): if len(sys.argv) != 4: print(f"Usage: {sys.argv[0]} ", file=sys.stderr) sys.exit(1) listen_port = int(sys.argv[1]) pg_host = sys.argv[2] pg_port = int(sys.argv[3]) # Write PID file with open('/tmp/evil_proxy.pid', 'w') as f: f.write(str(os.getpid())) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('127.0.0.1', listen_port)) server.listen(256) print(f"[PROXY] evil proxy pid={os.getpid()} listening on 127.0.0.1:{listen_port}, " f"forwarding to {pg_host}:{pg_port}", flush=True) while True: client_sock, addr = server.accept() t = threading.Thread(target=handle_client, args=(client_sock, pg_host, pg_port), daemon=True) t.start() if __name__ == '__main__': main() odyssey-1.6.0-rc1/test/functional/tests/cancel-leak/runner.sh000077500000000000000000000113011526064202200241650ustar00rootroot00000000000000#!/bin/bash -x # # Integration test: cancel request routing_clients leak # # Reproduces a production bug where od_backend_connect_cancel() hangs # forever on od_read() with UINT32_MAX timeout, leaking: # - routing_clients counter (never decremented) # - coroutines (stuck forever) # - socket file descriptors # # When enough routing slots leak, client_max_routing is reached and # the accept loop blocks ALL new connections — including admin console. # # Architecture: # PG:5432 <-- evil_proxy:6433 <-- odyssey:6432 <-- send_cancel.py # # The evil proxy forwards cancel requests to PG but keeps the client # socket open forever, simulating a network condition where EOF never # arrives back to Odyssey. # set -ex TEST_DIR=/tests/cancel-leak PROXY_PORT=6433 ODYSSEY_PORT=6432 CANCEL_COUNT=20 # --- Start evil proxy --- python3 "$TEST_DIR/evil_proxy.py" $PROXY_PORT 127.0.0.1 5432 \ > /tmp/evil_proxy.log 2>&1 & PROXY_PID=$! sleep 1 kill -0 $PROXY_PID || { echo "FAIL: evil proxy did not start" cat /tmp/evil_proxy.log exit 1 } cleanup() { kill $PROXY_PID 2>/dev/null || true rm -f /tmp/evil_proxy.pid ody-stop 2>/dev/null || true } trap cleanup EXIT # --- Start Odyssey --- /usr/bin/odyssey "$TEST_DIR/cancel-leak.conf" sleep 1 # --- Verify connectivity --- psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'SELECT 1' # --- Baseline: routing_clients should be 0 --- sleep 2 # let stats print once for baseline coroutine count get_routing_clients() { timeout 3 psql 'host=localhost port=6432 user=console dbname=console' \ -t -A -c 'show lists' 2>/dev/null | grep routing_clients | cut -d'|' -f2 | tr -d ' ' } get_coroutine_total() { # Sum active coroutines from all worker + system stats lines (last batch) local total=0 while IFS= read -r line; do local active active=$(echo "$line" | grep -oP 'coroutines \(\K[0-9]+') if [ -n "$active" ]; then total=$((total + active)) fi done < <(grep 'coroutines (' /var/log/odyssey.log | tail -3) echo "$total" } initial_rc=$(get_routing_clients) initial_coro=$(get_coroutine_total) echo "Initial: routing_clients=$initial_rc, coroutines=$initial_coro" if [ "$initial_rc" != "0" ]; then echo "FAIL: routing_clients not 0 at start ($initial_rc)" exit 1 fi # --- Fire parallel cancel requests --- echo "Firing $CANCEL_COUNT parallel cancel requests..." python3 "$TEST_DIR/send_cancel.py" 127.0.0.1 $ODYSSEY_PORT postgres postgres $CANCEL_COUNT # Wait for cancels to settle + stats to print sleep 5 # --- Check results --- after_coro=$(get_coroutine_total) echo "After cancels: coroutines=$after_coro (was $initial_coro)" # Try to read routing_clients — if blocked, that's the ultimate proof set +e after_rc=$(get_routing_clients) rc_exit=$? set -e if [ -z "$after_rc" ] || [ $rc_exit -ne 0 ]; then # Console is blocked — accept loop is saturated echo "Console BLOCKED — routing slots fully saturated" after_rc="BLOCKED" fi echo "After cancels: routing_clients=$after_rc" # --- Check for production log message --- cmr_hits=$(grep -c 'client is waiting in routing queue' /var/log/odyssey.log || echo 0) echo "Log 'client is waiting in routing queue': $cmr_hits hits" # --- Try a new data connection --- set +e timeout 5 psql 'host=localhost port=6432 user=postgres dbname=postgres' \ -c "SELECT 'canary'" > /tmp/canary.out 2>&1 canary_rc=$? set -e echo "Canary connection: exit_code=$canary_rc (124=timeout)" # --- Verify the bug is present (unfixed) or absent (fixed) --- coro_delta=$((after_coro - initial_coro)) echo "" echo "=== Results ===" echo " routing_clients: $initial_rc -> $after_rc" echo " coroutine delta: $coro_delta" echo " console blocked: $([ "$after_rc" = 'BLOCKED' ] && echo YES || echo no)" echo " canary blocked: $([ $canary_rc -eq 124 ] && echo YES || echo no)" echo " log cmr hits: $cmr_hits" # The test PASSes if the fix is in place: routing_clients returns to 0 # The test FAILs if the bug exists: routing_clients leaks and blocks everything if [ "$after_rc" = "0" ]; then echo "" echo "PASS: routing_clients returned to 0 — cancel leak fix is working" exit 0 fi # Bug detected echo "" echo "FAIL: cancel leak detected" if [ "$after_rc" = "BLOCKED" ]; then echo " Admin console is unreachable (accept loop blocked)" fi if [ $canary_rc -eq 124 ]; then echo " New data connections are blocked" fi if [ $cmr_hits -gt 0 ]; then echo " Log shows '(client_max_routing) client is waiting in routing queue'" fi echo " Coroutines leaked: $coro_delta" # Show diagnostic info echo "" echo "=== Proxy log ===" tail -20 /tmp/evil_proxy.log echo "" echo "=== Odyssey log (last 30 lines) ===" tail -30 /var/log/odyssey.log exit 1 odyssey-1.6.0-rc1/test/functional/tests/cancel-leak/send_cancel.py000066400000000000000000000072061526064202200251360ustar00rootroot00000000000000#!/usr/bin/env python3 """ Fire N parallel cancel requests against Odyssey. Each cancel: connect, start pg_sleep, send CancelRequest, close. All N cancels are launched in parallel threads. Usage: send_cancel.py [count] """ import socket import struct import sys import time import threading def _recv_exact(sock, n): buf = b'' while len(buf) < n: chunk = sock.recv(n - len(buf)) if not chunk: raise ConnectionError("connection closed") buf += chunk return buf def pg_connect(host, port, user, dbname): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(10) sock.connect((host, port)) params = f"user\x00{user}\x00database\x00{dbname}\x00\x00" msg = struct.pack('!II', 4 + 4 + len(params), 0x00030000) + params.encode() sock.sendall(msg) pid, secret = None, None while True: hdr = _recv_exact(sock, 5) msg_type = hdr[0:1] msg_len = struct.unpack('!I', hdr[1:5])[0] body = _recv_exact(sock, msg_len - 4) if msg_type == b'E': raise RuntimeError(f"PG error: {body.decode('utf-8', errors='replace')}") if msg_type == b'R': auth_type = struct.unpack('!I', body[:4])[0] if auth_type != 0: raise RuntimeError(f"Unsupported auth type: {auth_type}") if msg_type == b'K': pid, secret = struct.unpack('!II', body[:8]) if msg_type == b'Z': return sock, pid, secret def send_query(sock, query): q = query.encode() + b'\x00' msg = b'Q' + struct.pack('!I', 4 + len(q)) + q sock.sendall(msg) def send_cancel_packet(host, port, pid, secret): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.connect((host, port)) msg = struct.pack('!IIII', 16, 80877102, pid, secret) sock.sendall(msg) time.sleep(0.5) sock.close() def do_one_cancel(idx, host, port, user, dbname, results): try: sock, pid, secret = pg_connect(host, port, user, dbname) send_query(sock, "SELECT pg_sleep(600)") time.sleep(0.5) send_cancel_packet(host, port, pid, secret) # Read cancel response try: sock.settimeout(10) while True: hdr = _recv_exact(sock, 5) msg_type = hdr[0:1] msg_len = struct.unpack('!I', hdr[1:5])[0] body = _recv_exact(sock, msg_len - 4) if msg_type == b'Z': break except Exception: pass sock.close() results[idx] = "OK" print(f" cancel #{idx+1}: OK", flush=True) except Exception as e: results[idx] = f"FAIL: {e}" print(f" cancel #{idx+1}: FAIL: {e}", flush=True) def main(): if len(sys.argv) < 5: print(f"Usage: {sys.argv[0]} [count]", file=sys.stderr) sys.exit(1) host = sys.argv[1] port = int(sys.argv[2]) user = sys.argv[3] dbname = sys.argv[4] count = int(sys.argv[5]) if len(sys.argv) > 5 else 1 print(f"Firing {count} parallel cancel requests...", flush=True) results = [None] * count threads = [] for i in range(count): t = threading.Thread(target=do_one_cancel, args=(i, host, port, user, dbname, results)) threads.append(t) for t in threads: t.start() for t in threads: t.join(timeout=30) ok = sum(1 for r in results if r == "OK") fail = sum(1 for r in results if r and r.startswith("FAIL")) print(f"Done: {ok} OK, {fail} FAIL out of {count}", flush=True) if __name__ == '__main__': main() odyssey-1.6.0-rc1/test/functional/tests/cancel/000077500000000000000000000000001526064202200214075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/cancel/cancel.conf000066400000000000000000000007301526064202200235030ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_size 20 } } daemonize yes pid_file "/var/run/odyssey.pid" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/cancel/runner.sh000077500000000000000000000026161526064202200232640ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/cancel/cancel.conf sleep 1 # background load to ensure no one gets wrong cancel pgbench 'host=localhost port=6432 user=postgres dbname=postgres' -S -T 17 -j2 -c8 --no-vacuum 2>&1 >/tests/cancel/pgbenchlog & pgbenchpid=$! for i in {1..7}; do psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_sleep(10)' 2>/tests/cancel/log1 1>&2 & pid1=$! psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_sleep(10)' 2>/tests/cancel/log2 1>&2 & pid2=$! psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_sleep(10)' 2>/tests/cancel/log3 1>&2 & pid3=$! sleep 2 kill -s SIGINT $pid1 kill -s SIGINT $pid2 kill -s SIGINT $pid3 wait $pid1 || { cat /tests/cancel/log1 | grep "ERROR: canceling statement due to user request" -q || { echo "seems like cancel 1 failed" cat /tests/cancel/log1 exit 1 } } wait $pid2 || { cat /tests/cancel/log2 | grep "ERROR: canceling statement due to user request" -q || { echo "seems like cancel 2 failed" cat /tests/cancel/log2 exit 1 } } wait $pid3 || { cat /tests/cancel/log3 | grep "ERROR: canceling statement due to user request" -q || { echo "seems like cancel 3 failed" cat /tests/cancel/log3 exit 1 } } done wait $pgbenchpid || { cat /tests/cancel/pgbenchlog echo "seems like pgbench failed" exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/cascade/000077500000000000000000000000001526064202200215455ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/cascade/odyssey-gateway.conf000066400000000000000000000020041526064202200255460ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.gateway.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp/gateway" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.gateway.log" log_to_stdout no log_config yes log_session yes log_stats yes stats_interval 1 listen { host "127.0.0.1" port 7432 tls "require" tls_key_file "/tests/cascade/gateway.key" tls_cert_file "/tests/cascade/gateway.pem" tls_ca_file "/tests/cascade/allCA.pem" tls_protocols "tlsv1.2" } storage "postgres_server" { type "remote" # 8432, 9432 - not existed host, emulation of failed host host "[127.0.0.1]:6432,[127.0.0.1]:8432,[127.0.0.1]:6433,[127.0.0.1]:9432" tls "require" tls_ca_file "/tests/cascade/allCA.pem" tls_protocols "tlsv1.2" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/cascade/odyssey-root1.conf000066400000000000000000000014751526064202200251640ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.root1.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp/root1" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.root1.log" log_to_stdout no log_config yes log_session yes log_stats yes stats_interval 1 listen { host "127.0.0.1" port 6432 tls "require" tls_key_file "/tests/cascade/root.key" tls_cert_file "/tests/cascade/root.pem" tls_ca_file "/tests/cascade/allCA.pem" tls_protocols "tlsv1.2" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/cascade/odyssey-root2.conf000066400000000000000000000014751526064202200251650ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.root2.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp/root2" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.root2.log" log_to_stdout no log_config yes log_session yes log_stats yes stats_interval 1 listen { host "127.0.0.1" port 6433 tls "require" tls_key_file "/tests/cascade/root.key" tls_cert_file "/tests/cascade/root.pem" tls_ca_file "/tests/cascade/allCA.pem" tls_protocols "tlsv1.2" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/cascade/runner.sh000077500000000000000000000044741526064202200234260ustar00rootroot00000000000000#!/bin/bash set -ex pushd /tests/cascade/ rm -f allCA* rm -f root* rm -f gateway* openssl genrsa -out allCA.key 2048 openssl req -new -key allCA.key -out allCA.csr -nodes -subj "/CN=odyssey-test-cn" openssl x509 -req -days 2 -in allCA.csr -signkey allCA.key -out allCA.pem openssl genrsa -out root.key 2048 openssl req -new -key root.key -out root.csr -nodes -subj "/CN=localhost" openssl x509 -req -in root.csr -CA allCA.pem -CAkey allCA.key -CAcreateserial -out root.pem -days 2 openssl genrsa -out gateway.key 2048 openssl req -new -key gateway.key -out gateway.csr -nodes -subj "/CN=localhost" openssl x509 -req -in gateway.csr -CA allCA.pem -CAkey allCA.key -CAcreateserial -out gateway.pem -days 2 popd mkdir -p /tmp/gateway mkdir -p /tmp/root1 mkdir -p /tmp/root2 /usr/bin/odyssey /tests/cascade/odyssey-gateway.conf sleep 1 gateway_pid=$(pidof odyssey) /usr/bin/odyssey /tests/cascade/odyssey-root1.conf /usr/bin/odyssey /tests/cascade/odyssey-root2.conf sleep 1 psql 'host=localhost port=6432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/cascade/allCA.pem' -c 'select 1' || { cat /var/log/odyssey.root1.log cat /var/log/odyssey.root2.log cat /var/log/odyssey.gateway.log echo "select 1 for postgres:postgres on root1 should work with tls" exit 1 } psql 'host=localhost port=6433 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/cascade/allCA.pem' -c 'select 1' || { cat /var/log/odyssey.root1.log cat /var/log/odyssey.root2.log cat /var/log/odyssey.gateway.log echo "select 1 for postgres:postgres on root2 should work with tls" exit 1 } psql 'host=localhost port=7432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/cascade/allCA.pem' -c 'select 1' || { cat /var/log/odyssey.root1.log cat /var/log/odyssey.root2.log cat /var/log/odyssey.gateway.log echo "select 1 for postgres:postgres on gateway should work with tls" exit 1 } pgbench 'host=localhost port=7432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/cascade/allCA.pem' -j 10 -c 500 --select-only --no-vacuum --progress 1 -T 10 || { cat /var/log/odyssey.root1.log cat /var/log/odyssey.root2.log cat /var/log/odyssey.gateway.log echo "pgbench should work on gateway odyssey" exit 1 } ody-stop $gateway_pid ody-stopodyssey-1.6.0-rc1/test/functional/tests/client_max/000077500000000000000000000000001526064202200223055ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/client_max/odyssey.conf000066400000000000000000000010411526064202200246470ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_size 20 } } daemonize yes pid_file "/var/run/odyssey.pid" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no client_max 1 listen { host "127.0.0.1" port 6432 } listen { host "127.0.0.1" port 9999 reserved_clients 2 }odyssey-1.6.0-rc1/test/functional/tests/client_max/runner.sh000077500000000000000000000020331526064202200241530ustar00rootroot00000000000000#!/bin/bash -x set -ex ody-start2 /tests/client_max/odyssey.conf postgres postgres psql 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -c 'select pg_sleep(10000)' & p=$! sleep 0.1 if psql 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -c 'select 42'; then echo "the connect must fail" exit 1 fi # reserve_clients for listen allows to add some connections psql 'host=localhost port=9999 user=postgres dbname=postgres sslmode=disable' -c 'select pg_sleep(10000)' & p1=$! sleep 0.5 psql 'host=localhost port=9999 user=postgres dbname=postgres sslmode=disable' -c 'select pg_sleep(10000)' & p2=$! sleep 0.5 if psql 'host=localhost port=9999 user=postgres dbname=postgres sslmode=disable' -c 'select 42'; then echo "the connect must fail" exit 1 fi kill -9 $p2 kill -9 $p1 kill -9 $p sleep 0.1 psql 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -c 'select 42' psql 'host=localhost port=9999 user=postgres dbname=postgres sslmode=disable' -c 'select 42' ody-stop odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/000077500000000000000000000000001526064202200232435ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/bad_include.conf000066400000000000000000000010631526064202200263430ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no workers 2 resolvers 1 listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/bad_include.conf.autoconf000066400000000000000000000000551526064202200301600ustar00rootroot00000000000000include "/tests/config-autoconf/zone_a.conf" odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/main.conf000066400000000000000000000011121526064202200250310ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no workers 2 resolvers 1 availability_zone "a" listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/main.conf.autoconf000066400000000000000000000000261526064202200266510ustar00rootroot00000000000000availability_zone "b" odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/runner.sh000077500000000000000000000022521526064202200251140ustar00rootroot00000000000000#!/usr/bin/env bash # Autoconf tests. # # 1. main.conf sets availability_zone "a", while autoconf overrides it to "b". # Odyssey should start and report "b" via SHOW CONFIG. # 2. bad_include.conf.autoconf contains an include directive, which is forbidden # in autoconf. Odyssey must refuse to start. set -uex CONF=/tests/config-autoconf/main.conf BAD_CONF=/tests/config-autoconf/bad_include.conf # 1. Override global option in autoconf /usr/bin/odyssey "$CONF" sleep 1 psql -h 127.0.0.1 -p 6432 -U console -d console \ --quiet --no-align --tuples-only -F '|' -c 'show config' | grep -E '^availability_zone\|' | grep -q '^availability_zone|b' || { echo "expected availability_zone 'b' overridden by autoconf" >&2 cat /var/log/odyssey.log >&2 || true exit 1 } ody-stop # 2. Include is forbidden in autoconf if /usr/bin/odyssey "$BAD_CONF" >/tmp/bad_include.out 2>&1; then echo "autoconf with include must fail to start" >&2 cat /tmp/bad_include.out >&2 || true exit 1 fi grep -q "includes are forbidden in this context" /tmp/bad_include.out || { echo "expected 'includes are forbidden in this context' in startup output" >&2 cat /tmp/bad_include.out >&2 || true exit 1 } odyssey-1.6.0-rc1/test/functional/tests/config-autoconf/zone_a.conf000066400000000000000000000000261526064202200253630ustar00rootroot00000000000000availability_zone "a" odyssey-1.6.0-rc1/test/functional/tests/config-include/000077500000000000000000000000001526064202200230505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-include/common.conf000066400000000000000000000010621526064202200252060ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no workers 2 resolvers 1 listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/functional/tests/config-include/conf.conf000066400000000000000000000001271526064202200246440ustar00rootroot00000000000000include "/tests/config-include/common.conf" include "/tests/config-include/zone_a.conf"odyssey-1.6.0-rc1/test/functional/tests/config-include/dup.conf000066400000000000000000000002031526064202200245020ustar00rootroot00000000000000include "/tests/config-include/common.conf" include "/tests/config-include/zone_a.conf" include "/tests/config-include/zone_b.conf"odyssey-1.6.0-rc1/test/functional/tests/config-include/runner.sh000077500000000000000000000016331526064202200247230ustar00rootroot00000000000000#!/usr/bin/env bash # Config file inclusion. # # Including two config files that both set common options # is duplicate and leads to an error. set -uex CONF=/tests/config-include/conf.conf DUP_CONF=/tests/config-include/dup.conf /usr/bin/odyssey "$CONF" sleep 1 psql -h 127.0.0.1 -p 6432 -U console -d console \ --quiet --no-align --tuples-only -F '|' -c 'show config' | grep -E '^availability_zone\|' | grep -q '^availability_zone|a' || { echo "expected availability_zone 'a' from included config" >&2 cat /var/log/odyssey.log >&2 || true exit 1 } ody-stop # reject dups if /usr/bin/odyssey "$DUP_CONF" >/tmp/dup.out 2>&1; then echo "duplicate availability_zone must fail to start" >&2 cat /tmp/dup.out >&2 || true exit 1 fi grep -q "duplicate option 'availability_zone'" /tmp/dup.out || { echo "expected 'duplicate option availability_zone' in startup output" >&2 cat /tmp/dup.out >&2 || true exit 1 } odyssey-1.6.0-rc1/test/functional/tests/config-include/zone_a.conf000066400000000000000000000000251526064202200251670ustar00rootroot00000000000000availability_zone "a"odyssey-1.6.0-rc1/test/functional/tests/config-include/zone_b.conf000066400000000000000000000000251526064202200251700ustar00rootroot00000000000000availability_zone "b"odyssey-1.6.0-rc1/test/functional/tests/config-validation/000077500000000000000000000000001526064202200235575ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/000077500000000000000000000000001526064202200252075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/000077500000000000000000000000001526064202200273755ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/invalid/000077500000000000000000000000001526064202200310235ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000006261526064202200343540ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" auth_query "auth_query" auth_query_db "db" } }odyssey-test2.conf000066400000000000000000000006321526064202200343520ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" auth_query "auth_query" auth_query_user "user" } }odyssey-test3.conf000066400000000000000000000005631526064202200343560ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" auth_query "auth_query" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/valid/000077500000000000000000000000001526064202200304745ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000006751526064202200340310ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/auth_query/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" auth_query "auth_query" auth_query_user "user" auth_query_db "db" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/000077500000000000000000000000001526064202200302265ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/invalid/000077500000000000000000000000001526064202200316545ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005121526064202200351770ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "md5" } }odyssey-test2.conf000066400000000000000000000005241526064202200352030ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "scram-sha-256" } }odyssey-test3.conf000066400000000000000000000005121526064202200352010ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "kek" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/valid/000077500000000000000000000000001526064202200313255ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005131526064202200346510ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test10.conf000066400000000000000000000005131526064202200347310ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "cert" } }odyssey-test2.conf000066400000000000000000000005141526064202200346530ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "block" } }odyssey-test3.conf000066400000000000000000000005651526064202200346620ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "clear_text" password "password" } }odyssey-test4.conf000066400000000000000000000005561526064202200346630ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "md5" password "password" } }odyssey-test5.conf000066400000000000000000000006741526064202200346650ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "md5" auth_query "auth_query" auth_query_user "user" auth_query_db "db" } }odyssey-test6.conf000066400000000000000000000007061526064202200346620ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "scram-sha-256" auth_query "auth_query" auth_query_user "user" auth_query_db "db" } }odyssey-test7.conf000066400000000000000000000005701526064202200346620ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "scram-sha-256" password "password" } }odyssey-test8.conf000066400000000000000000000007401526064202200346620ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "md5" auth_query "auth_query" password "password" auth_query_user "user" auth_query_db "db" } }odyssey-test9.conf000066400000000000000000000007521526064202200346660ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/authentication/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "scram-sha-256" auth_query "auth_query" password "password" auth_query_user "user" auth_query_db "db" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/000077500000000000000000000000001526064202200314355ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/invalid/000077500000000000000000000000001526064202200330635ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001351526064202200364070ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/invalidcoroutine_stack_size 3 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test2.conf000066400000000000000000000001351526064202200364100ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/invalidcoroutine_stack_size 0 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test3.conf000066400000000000000000000001361526064202200364120ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/invalidcoroutine_stack_size -1 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test4.conf000066400000000000000000000001411526064202200364070ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/invalidcoroutine_stack_size "123" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test5.conf000066400000000000000000000010311526064202200364070ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/invalidcoroutine_stack_size 8 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } ldap_endpoint "ldap" { ldapserver "127.0.0.1" ldapport 389 ldapscheme "ldap" ldapbasedn "dc=local" ldapbinddn "dc=local" ldapbindpasswd "pass" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" ldap_endpoint_name "ldap" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/valid/000077500000000000000000000000001526064202200325345ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005041526064202200360600ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/validcoroutine_stack_size 4 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000005051526064202200360620ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/validcoroutine_stack_size 10 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test3.conf000066400000000000000000000010311526064202200360560ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/coroutine_stack_size/validcoroutine_stack_size 16 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } ldap_endpoint "ldap" { ldapserver "127.0.0.1" ldapport 389 ldapscheme "ldap" ldapbasedn "dc=local" ldapbinddn "dc=local" ldapbindpasswd "pass" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" ldap_endpoint_name "ldap" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/000077500000000000000000000000001526064202200275555ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/invalid/000077500000000000000000000000001526064202200312035ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000006631526064202200345350ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { group "group1" { storage "postgres_server" pool "session" authentication "none" group_query_user "group_query_user" group_query_db "group_query_db" } } odyssey-test2.conf000066400000000000000000000007511526064202200345340ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { group "group1" { storage "postgres_server" pool "session" authentication "none" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group1', 'member')" group_query_db "group_query_db" } } odyssey-test3.conf000066400000000000000000000007561526064202200345420ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { group "group1" { storage "postgres_server" pool "session" authentication "none" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group1', 'member')" group_query_user "group_query_user" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/valid/000077500000000000000000000000001526064202200306545ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000010351526064202200342000ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/group_rules/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { group "group1" { storage "postgres_server" pool "session" authentication "none" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group1', 'member')" group_query_user "group_query_user" group_query_db "group_query_db" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_empty/000077500000000000000000000000001526064202200277235ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_empty/invalid/000077500000000000000000000000001526064202200313515ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000000631526064202200346750ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_empty/invalidworkers 0 log_format "%p %t %l [%i %s] (%c) %m\n" odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_empty/valid/000077500000000000000000000000001526064202200310225ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004671526064202200343560ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_empty/validworkers 1 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/000077500000000000000000000000001526064202200273675ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/invalid/000077500000000000000000000000001526064202200310155ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001241526064202200343370ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "" } odyssey-test2.conf000066400000000000000000000001271526064202200343430ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "123" } odyssey-test3.conf000066400000000000000000000001321526064202200343400ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "disabl" } odyssey-test4.conf000066400000000000000000000001311526064202200343400ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls disable } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/valid/000077500000000000000000000000001526064202200304665ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005021526064202200340100ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "disable" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000005001526064202200340070ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "allow" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test3.conf000066400000000000000000000005021526064202200340120ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "require" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test4.conf000066400000000000000000000005041526064202200340150ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "verify_ca" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test5.conf000066400000000000000000000005061526064202200340200ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/listen_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "verify_full" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/log_format/000077500000000000000000000000001526064202200273405ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/log_format/invalid/000077500000000000000000000000001526064202200307665ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000000341526064202200343100ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/log_format/invalidlisten { host "*" } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/log_format/valid/000077500000000000000000000000001526064202200304375ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004551526064202200337700ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/log_format/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/000077500000000000000000000000001526064202200336615ustar00rootroot00000000000000invalid/000077500000000000000000000000001526064202200352305ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statementodyssey-test1.conf000066400000000000000000000006421526064202200406360ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" pool_reserve_prepared_statement yes pool_discard yes } }odyssey-test2.conf000066400000000000000000000005771526064202200406460ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_reserve_prepared_statement yes } }odyssey-test3.conf000066400000000000000000000006471526064202200406450ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" pool_reserve_prepared_statement no pool_smart_discard yes } }odyssey-test4.conf000066400000000000000000000006501526064202200406400ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" pool_reserve_prepared_statement yes pool_discard_query yes } }valid/000077500000000000000000000000001526064202200347015ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statementodyssey-test1.conf000066400000000000000000000006411526064202200403060ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" pool_reserve_prepared_statement yes pool_discard no } }odyssey-test2.conf000066400000000000000000000006001526064202200403020ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" pool_reserve_prepared_statement no } }odyssey-test3.conf000066400000000000000000000007111526064202200403060ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" pool_reserve_prepared_statement yes pool_discard_query "no" pool_discard no } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_routing/000077500000000000000000000000001526064202200277275ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_routing/invalid/000077500000000000000000000000001526064202200313555ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005561526064202200347100ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_routing/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "kek" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_routing/valid/000077500000000000000000000000001526064202200310265ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005631526064202200343570ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_routing/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "internal" } }odyssey-test2.conf000066400000000000000000000005711526064202200343570ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_routing/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "client_visible" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/000077500000000000000000000000001526064202200272215ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/invalid/000077500000000000000000000000001526064202200306475ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004541526064202200341770ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" authentication "none" } }odyssey-test2.conf000066400000000000000000000005071526064202200341770ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "kek" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/valid/000077500000000000000000000000001526064202200303205ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/valid/odyssey-test1.conf000066400000000000000000000005131526064202200337230ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/valid/odyssey-test2.conf000066400000000000000000000005171526064202200337300ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "transaction" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/pool_type/valid/odyssey-test3.conf000066400000000000000000000005151526064202200337270ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "statement" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/000077500000000000000000000000001526064202200272335ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/invalid/000077500000000000000000000000001526064202200306615ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001221526064202200342010ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/invalidresolvers 0 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test2.conf000066400000000000000000000001241526064202200342040ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/invalidresolvers -10 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test3.conf000066400000000000000000000001251526064202200342060ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/invalidresolvers "10" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/valid/000077500000000000000000000000001526064202200303325ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/valid/odyssey-test1.conf000066400000000000000000000004721526064202200337410ustar00rootroot00000000000000resolvers 1 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/resolvers/valid/odyssey-test2.conf000066400000000000000000000004721526064202200337420ustar00rootroot00000000000000resolvers 10 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/rules_empty/000077500000000000000000000000001526064202200275575ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/rules_empty/invalid/000077500000000000000000000000001526064202200312055ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000003541526064202200345340ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/rules_empty/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } database "db" { user "user" { pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000002521526064202200345320ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/rules_empty/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/rules_empty/valid/000077500000000000000000000000001526064202200306565ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005131526064202200342020ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/rules_empty/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_name/000077500000000000000000000000001526064202200276535ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_name/invalid/000077500000000000000000000000001526064202200313015ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004411526064202200346250ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_name/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000005041526064202200346260ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_name/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_name/valid/000077500000000000000000000000001526064202200307525ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005131526064202200342760ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_name/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/000077500000000000000000000000001526064202200275355ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/invalid/000077500000000000000000000000001526064202200311635ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000002131526064202200345040ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "123" } odyssey-test2.conf000066400000000000000000000002161526064202200345100ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "disabl" } odyssey-test3.conf000066400000000000000000000002151526064202200345100ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls disable } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/valid/000077500000000000000000000000001526064202200306345ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004601526064202200341610ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "disable" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000004561526064202200341670ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "allow" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test3.conf000066400000000000000000000004601526064202200341630ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "require" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test4.conf000066400000000000000000000004621526064202200341660ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "verify_ca" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test5.conf000066400000000000000000000004641526064202200341710ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_tls/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" tls "verify_full" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/000077500000000000000000000000001526064202200277145ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalid/000077500000000000000000000000001526064202200313425ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000000641526064202200346670ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { } odyssey-test2.conf000066400000000000000000000001721526064202200346700ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } odyssey-test3.conf000066400000000000000000000001441526064202200346700ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { } odyssey-test4.conf000066400000000000000000000006201526064202200346700ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "internal" role "admin" } }odyssey-test5.conf000066400000000000000000000006171526064202200346770ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "internal" role "stat" } }odyssey-test6.conf000066400000000000000000000006231526064202200346750ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "internal" role "notallow" } }odyssey-test7.conf000066400000000000000000000006171526064202200347010ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" pool_routing "internal" role "undef" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/valid/000077500000000000000000000000001526064202200310135ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004541526064202200343430ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { host "*" type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000005131526064202200343400ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test3.conf000066400000000000000000000004321526064202200343410ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/storage_type/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "local" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/unix_socket_mode/000077500000000000000000000000001526064202200305465ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/unix_socket_mode/invalid/000077500000000000000000000000001526064202200321745ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001351526064202200355200ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/unix_socket_mode/invalidunix_socket_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/unix_socket_mode/valid/000077500000000000000000000000001526064202200316455ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005341526064202200351740ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/unix_socket_mode/validunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-test2.conf000066400000000000000000000004541526064202200351760ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/unix_socket_mode/validlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/watchdog/000077500000000000000000000000001526064202200270075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/watchdog/invalid/000077500000000000000000000000001526064202200304355ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000013051526064202200337610ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/watchdog/invalidunix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" pid_file "/var/run/odyssey.pid" daemonize yes log_to_stdout yes workers 5 resolvers 1 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5433 watchdog { authentication "none" storage "unknown_storage" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM pg_last_xact_replay_timestamp()))" watchdog_lag_interval 1 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/watchdog/valid/000077500000000000000000000000001526064202200301065ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/watchdog/valid/odyssey-test1.conf000066400000000000000000000020761526064202200335170ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" pid_file "/var/run/odyssey.pid" daemonize yes log_to_stdout yes workers 5 resolvers 1 listen { host "*" port 6432 backlog 128 } storage "postgres_server1" { type "remote" host "localhost" port 5433 watchdog { authentication "none" storage "postgres_server1" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM pg_last_xact_replay_timestamp()))" watchdog_lag_interval 1 } } storage "postgres_server2" { type "remote" host "localhost" port 5434 watchdog { authentication "none" storage "postgres_server2" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM pg_last_xact_replay_timestamp()))" watchdog_lag_interval 1 } } database default { user default { authentication "none" storage "postgres_server1" pool "transaction" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/000077500000000000000000000000001526064202200267035ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/invalid/000077500000000000000000000000001526064202200303315ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/invalid/odyssey-test1.conf000066400000000000000000000004701526064202200337360ustar00rootroot00000000000000workers 0 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/invalid/odyssey-test2.conf000066400000000000000000000004721526064202200337410ustar00rootroot00000000000000workers -10 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/invalid/odyssey-test3.conf000066400000000000000000000004741526064202200337440ustar00rootroot00000000000000workers "123" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/valid/000077500000000000000000000000001526064202200300025ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/valid/odyssey-test1.conf000066400000000000000000000004701526064202200334070ustar00rootroot00000000000000workers 1 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } } odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/valid/odyssey-test2.conf000066400000000000000000000004701526064202200334100ustar00rootroot00000000000000workers 10 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/configs/workers/valid/odyssey-test3.conf000066400000000000000000000004741526064202200334150ustar00rootroot00000000000000workers "auto" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } storage "postgres_server" { type "remote" host "*" } database "db" { user "user" { storage "postgres_server" pool "session" authentication "none" } }odyssey-1.6.0-rc1/test/functional/tests/config-validation/go.mod000066400000000000000000000000441526064202200246630ustar00rootroot00000000000000module config-validation go 1.23.4 odyssey-1.6.0-rc1/test/functional/tests/config-validation/go.sum000066400000000000000000000000001526064202200247000ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/pkg/000077500000000000000000000000001526064202200243405ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/config-validation/pkg/main.go000066400000000000000000000032221526064202200256120ustar00rootroot00000000000000package main import ( "context" "errors" "fmt" "io/ioutil" "os/exec" "strings" ) const pathPrefix = "/tests/config-validation/configs" const configIsValid = "config is valid" const configWithInvalidValuePass = "config with invalid value pass" func makeTest(pathToConfig string, isValidConfig bool) error { ctx := context.TODO() out, _ := exec.CommandContext(ctx, "/usr/bin/odyssey", pathToConfig, "--test").Output() strOut := string(out) if isValidConfig && !strings.Contains(strOut, configIsValid) { return errors.New(strOut) } if !isValidConfig && strings.Contains(strOut, configIsValid) { return errors.New(configWithInvalidValuePass) } return nil } func makeTests(field string, isValid bool) { var group string if isValid { group = "valid" } else { group = "invalid" } pathToDir := pathPrefix + "/" + field + "/" + group configs, _ := ioutil.ReadDir(pathToDir) for ind, config := range configs { pathToConfig := pathToDir + "/" + config.Name() if err := makeTest(pathToConfig, isValid); err != nil { fmt.Printf("%s_test_%s_%d (ERROR): %s\n", field, group, ind, err) } else { fmt.Printf("%s_test_%s_%d: Ok\n", field, group, ind) } } } func runTests() { tests := []string{ "workers", "resolvers", "coroutine_stack_size", "log_format", "unix_socket_mode", "listen_empty", "listen_tls", "storage_type", "storage_tls", "storage_name", "pool_type", "pool_reserve_prepared_statement", "pool_routing", "authentication", "auth_query", "rules_empty", "group_rules", "watchdog", } for _, test := range tests { makeTests(test, true) makeTests(test, false) } } func main() { runTests() } odyssey-1.6.0-rc1/test/functional/tests/config-validation/runner.sh000077500000000000000000000001441526064202200254260ustar00rootroot00000000000000#!/usr/bin/env bash set -ex ody-start sleep 1 /tests/config-validation/config-validation ody-stop odyssey-1.6.0-rc1/test/functional/tests/copy/000077500000000000000000000000001526064202200211345ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/copy/config.conf000066400000000000000000000021451526064202200232520ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_file "/var/log/odyssey.log" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers "auto" resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "[localhost]:5432,localhost" port 5550 } database "db" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_discard no pool_reserve_prepared_statement yes client_fwd_error yes } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/copy/config_without_pstmt.conf000066400000000000000000000021441526064202200262630ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_file "/var/log/odyssey.log" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers "auto" resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "[localhost]:5432,localhost" port 5550 } database "db" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_discard no pool_reserve_prepared_statement no client_fwd_error yes } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/copy/runner.sh000077500000000000000000000060761526064202200230150ustar00rootroot00000000000000#!/bin/bash -x set -eux csv_file=$(mktemp) for i in {1..1000}; do echo "run_id_${i},task_id_${i},Some random ${i}th text" >> $csv_file done sort $csv_file -o $csv_file PSQL="psql postgresql://postgres@localhost:6432/db" get_server_ptr() { result=$(psql postgresql://console@localhost:6432/console -t -c "show servers;" | grep -Eo 's[0-9a-f]{12}') count=$(echo "$result" | wc -l) if [ "$count" -ne 1 ]; then echo "ERROR: expected 1 server connection, got $count" >&2 exit 1 fi echo "$result" } test_copy_in() { $PSQL -c "TRUNCATE copy_test;" || return 1 cat $csv_file | $PSQL -c "COPY copy_test FROM STDIN (FORMAT csv);" || return 1 count=$($PSQL -t -c "SELECT COUNT(*) FROM copy_test;" | tr -d ' ') if [ "$count" -ne 1000 ]; then echo "ERROR: expected 1000 rows, got $count" return 1 fi for i in $(seq 1 50 1000); do row=$($PSQL -t -c "SELECT description FROM copy_test WHERE run_id = 'run_id_${i}';" | tr -d ' ') if [ "$row" != "Somerandom${i}thtext" ]; then echo "ERROR: unexpected row content: $row" return 1 fi done return 0 } test_copy_out() { tmpfile=$(mktemp) $PSQL -c "COPY copy_test TO STDOUT (FORMAT csv);" > "$tmpfile" || { rm "$tmpfile"; return 1; } line_count=$(wc -l < "$tmpfile") if [ "$line_count" -ne 1000 ]; then echo "ERROR: copy out: expected 1000 lines, got $line_count" rm "$tmpfile" return 1 fi sort "$tmpfile" -o "$tmpfile" diff $csv_file $tmpfile || { echo "ERROR: copy out data mismatch" return 1 } rm $tmpfile return 0 } test_copy_in_error() { $PSQL -c "select 42" ptr_before=$(get_server_ptr) if [ -z "$ptr_before" ]; then echo "ERROR: no server connection before copy error test" return 1 fi echo "bad,data,with,too,many,columns,here" | \ $PSQL -c "COPY copy_test FROM STDIN (FORMAT csv);" 2>&1 && { echo "ERROR: expected error on bad data, but got success" return 1 } ptr_after=$(get_server_ptr) if [ "$ptr_before" != "$ptr_after" ]; then echo "ERROR: server connection changed after copy error: $ptr_before -> $ptr_after" return 1 fi return 0 } run_tests() { local result=0 test_copy_in || { echo "FAIL: copy in"; result=1; } test_copy_out || { echo "FAIL: copy out"; result=1; } test_copy_in_error || { echo "FAIL: copy in error handling"; result=1; } return $result } /usr/bin/odyssey /tests/copy/config.conf sleep 1 $PSQL -c " DROP TABLE IF EXISTS copy_test; CREATE TABLE copy_test ( run_id TEXT, task_id TEXT, description TEXT ); " with_pstmts_ok=0 run_tests && with_pstmts_ok=1 ody-stop echo "" > /var/log/odyssey.log /usr/bin/odyssey /tests/copy/config_without_pstmt.conf sleep 1 without_pstmts_ok=0 run_tests && without_pstmts_ok=1 ody-stop dump_logs() { cat /var/log/odyssey.log echo "" cat /var/log/postgresql/postgresql-16-main.log } if [ $with_pstmts_ok -eq 0 -a $without_pstmts_ok -eq 1 ]; then echo "ERROR: copy bug specific to pool_reserve_prepared_statement=yes" dump_logs exit 1 fi if [ $with_pstmts_ok -eq 0 -o $without_pstmts_ok -eq 0 ]; then echo "ERROR: copy bug" dump_logs exit 1 fi echo "OK: all copy tests passed" exit 0odyssey-1.6.0-rc1/test/functional/tests/external_auth/000077500000000000000000000000001526064202200230255ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/external_auth/external_auth.conf000066400000000000000000000010041526064202200265320ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "external" storage "postgres_server" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" locks_dir "/tmp" external_auth_socket_path "/tmp/test_external_auth.sock" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug yes log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/external_auth/go.mod000066400000000000000000000000441526064202200241310ustar00rootroot00000000000000module external-auth-agent go 1.21 odyssey-1.6.0-rc1/test/functional/tests/external_auth/pkg/000077500000000000000000000000001526064202200236065ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/external_auth/pkg/external-auth-agent.go000066400000000000000000000060721526064202200300170ustar00rootroot00000000000000package main import ( "encoding/binary" "fmt" "io" "log" "net" "os" ) func main() { // Remove existing socket file if it exists socketPath := "/tmp/test_external_auth.sock" if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { log.Printf("Warning: failed to remove existing socket: %v", err) } // Create Unix socket listener listener, err := net.Listen("unix", socketPath) if err != nil { log.Fatalf("Failed to create Unix socket listener: %v", err) } defer listener.Close() defer os.Remove(socketPath) fmt.Printf("External auth agent listening on %s\n", socketPath) authCount := 0 for { conn, err := listener.Accept() if err != nil { log.Printf("Failed to accept connection: %v", err) continue } authCount++ go handleConnection(conn, authCount) } } func handleConnection(conn net.Conn, authNum int) { defer conn.Close() fmt.Printf("Authentication #%d: Handling connection\n", authNum) // Read two messages message1, err := readMessage(conn) if err != nil { log.Printf("Auth #%d: Failed to read first message: %v", authNum, err) return } if string(message1[:len(message1)-1]) != "postgres" { log.Printf("Auth #%d: Received first message: %s, expected 'postgres'", authNum, message1) sendFailure(conn) return } fmt.Printf("Auth #%d: Received first message: %s\n", authNum, message1) message2, err := readMessage(conn) if err != nil { log.Printf("Auth #%d: Failed to read second message: %v", authNum, err) return } if string(message2[:len(message2)-1]) != "some-token" { log.Printf("Auth #%d: Received second message: %s, expected 'some-token'", authNum, message2) sendFailure(conn) return } fmt.Printf("Auth #%d: Received second message: %s\n", authNum, message2) sendSuccess(conn) fmt.Printf("Auth #%d: Connection handled successfully\n", authNum) } func sendSuccess(conn net.Conn) { sendMessage(conn, []byte{1}) sendMessage(conn, []byte("test-id")) } func sendFailure(conn net.Conn) { sendMessage(conn, []byte{0}) sendMessage(conn, []byte("failed-id")) } func readMessage(conn net.Conn) ([]byte, error) { // Read 8 bytes for size (uint64) sizeBuf := make([]byte, 8) if _, err := io.ReadFull(conn, sizeBuf); err != nil { return nil, fmt.Errorf("failed to read size: %v", err) } // Convert to uint64 (little-endian) size := binary.LittleEndian.Uint64(sizeBuf) if size > 1024*1024 { // 1MB limit for safety return nil, fmt.Errorf("message size too large: %d", size) } // Read the actual message msgBuf := make([]byte, size) if _, err := io.ReadFull(conn, msgBuf); err != nil { return nil, fmt.Errorf("failed to read message: %v", err) } return msgBuf, nil } func sendMessage(conn net.Conn, data []byte) error { // Send size first (8 bytes, uint64, little-endian) sizeBuf := make([]byte, 8) binary.LittleEndian.PutUint64(sizeBuf, uint64(len(data))) if _, err := conn.Write(sizeBuf); err != nil { return fmt.Errorf("failed to write size: %v", err) } // Send the actual data if _, err := conn.Write(data); err != nil { return fmt.Errorf("failed to write data: %v", err) } return nil } odyssey-1.6.0-rc1/test/functional/tests/external_auth/runner.sh000077500000000000000000000042131526064202200246750ustar00rootroot00000000000000#!/bin/bash set -eux /usr/bin/odyssey /tests/external_auth/external_auth.conf sleep 1 psql 'host=localhost port=6432 user=postgres dbname=postgres password=some-token' -c 'select current_user' 2>&1 && { echo "Test authentication successful, but it should have failed" echo '-----------' cat /tests/external_auth/agent.log exit 1 } /tests/external_auth/external-auth-agent 1>/tests/external_auth/agent.log 2>&1 & agent_pid=$! sleep 1 echo "Running first authentication attempt..." psql 'host=localhost port=6432 user=postgres dbname=postgres password=some-token' -c 'select current_user' 2>&1 || { echo "First authentication failed" echo '-----------' cat /tests/external_auth/agent.log exit 1 } echo "Running second authentication attempt..." psql 'host=localhost port=6432 user=postgres dbname=postgres password=some-token' -c 'select 1' 2>&1 || { echo "Second authentication failed" echo '-----------' cat /tests/external_auth/agent.log echo '-----------' cat /var/log/odyssey.log exit 1 } echo "Running third authentication attempt..." psql 'host=localhost port=6432 user=postgres dbname=postgres password=wrong-token' -c 'select 1' 2>&1 && { echo "Third authentication successful, but it should have failed" echo '-----------' cat /tests/external_auth/agent.log echo '-----------' cat /var/log/odyssey.log exit 1 } echo "All authentications tests completed successfully" pgbench 'host=localhost port=6432 user=postgres dbname=postgres password=some-token' -j 2 -c 10 --select-only --no-vacuum --progress 1 -T 10 || { echo "pgbench failed" echo '-----------' cat /tests/external_auth/agent.log echo '-----------' cat /var/log/odyssey.log exit 1 } pgbench 'host=localhost port=6432 user=postgres dbname=postgres password=some-token' -j 2 -c 10 --select-only --no-vacuum --progress 1 -T 10 --connect || { echo "pgbench failed" echo '-----------' cat /tests/external_auth/agent.log echo '-----------' cat /var/log/odyssey.log exit 1 } pgbench_result=$? ody-stop # Clean up kill -9 $agent_pid 2>/dev/null || true echo "Test completed successfully"odyssey-1.6.0-rc1/test/functional/tests/gorm/000077500000000000000000000000001526064202200211265ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/gorm/runner.sh000077500000000000000000000005401526064202200227750ustar00rootroot00000000000000set -ex ody-start git clone --depth 1 https://github.com/pg-sharding/gorm-spqr.git /gorm/gorm-spqr docker build -t gorm-tests /gorm/gorm-spqr rm -rf /gorm/gorm-spqr docker run -e DB_HOST='odyssey' -e DB_PORT=6432 -e DB_USER='spqr-console' -e DB_NAME='spqr-console' -e EXTRA_PARAMS='client_encoding=UTF8' --network=odyssey_od_net gorm-tests ody-stop odyssey-1.6.0-rc1/test/functional/tests/graceful-shutdown-timeout/000077500000000000000000000000001526064202200253075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/graceful-shutdown-timeout/config.conf000066400000000000000000000010771526064202200274300ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } daemonize no pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no graceful_shutdown_timeout_ms 5000 conn_drop_options { drop_enabled no } listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/graceful-shutdown-timeout/runner.sh000077500000000000000000000010241526064202200271540ustar00rootroot00000000000000#!/bin/bash -x set -exu /usr/bin/odyssey /tests/graceful-shutdown-timeout/config.conf & odyssey_pid=$! sleep 1 psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select 1' psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_sleep(10)' & query_pid=$! sleep 1 start_time=$(date +%s) kill -s TERM $odyssey_pid wait $odyssey_pid || true end_time=$(date +%s) elapsed=$((end_time - start_time)) if [ $elapsed -lt 5 ]; then echo "Shutdown timeout was configured to 5 seconds" exit 1 fi odyssey-1.6.0-rc1/test/functional/tests/group/000077500000000000000000000000001526064202200213165ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/group/config.conf000066400000000000000000000063401526064202200234350ustar00rootroot00000000000000listen { host "*" port 6432 } storage "postgres_server" { type "remote" host "localhost" port 5432 } database "postgres" { user "postgres" { authentication "none" pool_routing "internal" storage "postgres_server" pool "session" } } database "group_db" { group "group1" { authentication "md5" password "password1" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "client_visible" pool "session" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group1', 'member');" group_query_user "postgres" group_query_db "postgres" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage_password "passwd" } group "group2" { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "client_visible" pool "session" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group2', 'member');" group_query_user "postgres" group_query_db "postgres" } group "group3" "127.0.0.0/24" { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "client_visible" pool "session" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group3', 'member');" group_query_user "postgres" group_query_db "postgres" } group "group4" "255.0.0.0/24" { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "client_visible" pool "session" group_query "SELECT rolname FROM pg_roles WHERE pg_has_role(rolname, 'group4', 'member');" group_query_user "postgres" group_query_db "postgres" } user default { authentication "block" storage "postgres_server" pool "session" } } database default { user default { authentication "block" storage "postgres_server" pool "session" pool_size 0 pool_timeout 0 pool_ttl 1201 pool_discard no pool_cancel yes pool_rollback yes # seconds pool_client_idle_timeout 20 # seconds pool_idle_in_transaction_timeout 20 client_fwd_error yes application_name_add_host yes server_lifetime 1901 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } unix_socket_dir "/tmp" unix_socket_mode "0644" log_file "/var/log/odyssey.log" log_format "%p(%T) %t %l [%i %s] (%c) %m\n" log_debug no log_config yes log_session no log_query no log_stats yes daemonize yes locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes stats_interval 60 group_checker_interval 500 pid_file "/var/run/odyssey.pid" odyssey-1.6.0-rc1/test/functional/tests/group/runner.sh000077500000000000000000000064331526064202200231740ustar00rootroot00000000000000#!/bin/bash -x set -ex psql -h localhost -p 5432 -U postgres -c "REVOKE group1 FROM group_user1;" group_db || true psql -h localhost -p 5432 -U postgres -c "REVOKE group1 FROM group_user2;" group_db || true psql -h localhost -p 5432 -U postgres -c "REVOKE group2 FROM group_user3;" group_db || true psql -h localhost -p 5432 -U postgres -c "REVOKE group2 FROM group_user4;" group_db || true psql -h localhost -p 5432 -U postgres -c "REVOKE group3 FROM group_user6;" group_db || true psql -h localhost -p 5432 -U postgres -c "REVOKE group4 FROM group_user7;" group_db || true /usr/bin/odyssey /tests/group/config.conf sleep 1 users=("group_user1" "group_user2" "group_user3" "group_user4" "group_user5" "group_user6" "group_user7") for user in "${users[@]}"; do psql -h localhost -p 6432 -U "$user" -c "SELECT 1" group_db && { echo "ERROR: Authenticated with non-grouped user" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } done ody-stop || { cat /var/log/odyssey.log exit 1 } psql -h localhost -p 5432 -U postgres -c "GRANT group1 TO group_user1;" group_db psql -h localhost -p 5432 -U postgres -c "GRANT group1 TO group_user2;" group_db psql -h localhost -p 5432 -U postgres -c "GRANT group2 TO group_user3;" group_db psql -h localhost -p 5432 -U postgres -c "GRANT group2 TO group_user4;" group_db psql -h localhost -p 5432 -U postgres -c "GRANT group3 TO group_user6;" group_db psql -h localhost -p 5432 -U postgres -c "GRANT group4 TO group_user7;" group_db echo "" > /var/log/odyssey.log /usr/bin/odyssey /tests/group/config.conf sleep 3 psql -h localhost -p 6432 -U group_user1 -c "SELECT 1" group_db >/dev/null 2>&1 && { echo "ERROR: Authenticated without password" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } PGPASSWORD=password1 psql -h localhost -p 6432 -U group_user1 -c "SELECT 1" group_db || { echo "ERROR: Not authenticated with correct password" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } psql -h localhost -p 6432 -U group_user3 -c "SELECT 1" group_db || { echo "ERROR: Not authenticated with disabled auth" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } psql -h ip4-localhost -p 6432 -U group_user6 -c "SELECT 1" group_db || { echo "ERROR: Not authenticated with correct addr" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } psql -h ip4-localhost -p 6432 -U group_user7 -c "SELECT 1" group_db && { echo "ERROR: Authenticated with incorrect addr" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/repl/ -o '-p 5433' stop sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/main/ stop sleep 2 sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/main/ start sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/repl/ -o '-p 5433' start psql -h ip4-localhost -p 6432 -U group_user7 -c "SELECT 1" group_db && { echo "Break by falling postgres" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stopodyssey-1.6.0-rc1/test/functional/tests/hba/000077500000000000000000000000001526064202200207145ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/hba/common.conf000066400000000000000000000024511526064202200230550ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "hba_db" { user "user_allow" { authentication "clear_text" password "correct_password" storage "postgres_server" storage_db "hba_db" pool "session" } user "user_reject" { authentication "clear_text" password "correct_password" storage "postgres_server" storage_db "hba_db" pool "session" } user "user_unknown" { authentication "clear_text" password "correct_password" storage "postgres_server" storage_db "hba_db" pool "session" } } database default { user default { authentication "clear_text" password "correct_password" storage "postgres_server" storage_db "hba_db" storage_user "user_allow" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no hba_file "/tests/hba/odyssey_hba.conf" odyssey-1.6.0-rc1/test/functional/tests/hba/odyssey_hba.conf000066400000000000000000000006011526064202200240710ustar00rootroot00000000000000local hba_db user_allow allow local hba_db user_reject deny host hba_db user_allow 127.0.0.0/24 allow host hba_db user_reject 127.0.0.0/24 deny host hba_default_db hba_denied 127.0.0.0/24 deny host hba_default_db all 127.0.0.0/24 allow host sameuser all 127.0.0.0/24 allow odyssey-1.6.0-rc1/test/functional/tests/hba/runner.sh000077500000000000000000000046431526064202200225730ustar00rootroot00000000000000#!/bin/bash -x set -ex # # TCP # /usr/bin/odyssey /tests/hba/tcp.conf sleep 1 PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_allow -c "SELECT 1" hba_db || { echo "ERROR: failed auth with hba trust, correct password and plain password in config" sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } PGPASSWORD=incorrect_password psql -h ip4-localhost -p 6432 -U user_allow -c "SELECT 1" hba_db && { echo "ERROR: successfully auth with hba trust, but incorrect password" sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_reject -c "SELECT 1" hba_db && { echo "ERROR: successfully auth with hba reject" cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_unknown -c "SELECT 1" hba_db && { echo "ERROR: successfully auth without hba rule" sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } kill -s HUP $(pgrep odyssey) sleep 1 PGPASSWORD=correct_password PGCONNECT_TIMEOUT=5 psql -h ip4-localhost -p 6432 -U user_allow -c "SELECT 1" hba_db || { echo "ERROR: unable to connect after SIGHUP" sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } bash /tests/hba/test_hba_names.sh ip4-localhost ody-stop # # Unix # echo "" > /var/log/odyssey.log /usr/bin/odyssey /tests/hba/unix.conf sleep 1 PGPASSWORD=correct_password psql -h /tmp -p 6432 -U user_allow -c "SELECT 1" hba_db || { echo "ERROR: failed auth with hba trust, correct password and plain password in config" sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } PGPASSWORD=correct_password psql -h /tmp -p 6432 -U user_reject -c "SELECT 1" hba_db && { echo "ERROR: successfully auth with hba reject" sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i done echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/hba/tcp.conf000066400000000000000000000001021526064202200223420ustar00rootroot00000000000000include "/tests/hba/common.conf" listen { host "*" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/hba/test_hba_names.sh000066400000000000000000000021121526064202200242200ustar00rootroot00000000000000#!/bin/bash set -euo pipefail export PGPASSWORD=correct_password export PGCONNECT_TIMEOUT=5 export PGSSLMODE=disable host=$1 port=${PGPORT:-6432} query() { psql -X -w -v ON_ERROR_STOP=1 -At -h "$host" -p "$port" \ -d "$1" -U "$2" -c 'SELECT current_user, current_database()' } check_allowed() { local result result=$(query "$1" "$2") if [[ "$result" != 'user_allow|hba_db' ]]; then echo "ERROR: unexpected backend identity for $1/$2: $result" exit 1 fi echo "OK: HBA allows $1/$2 via $host" } check_rejected() { local result if result=$(query "$1" "$2" 2>&1); then echo "ERROR: HBA allowed $1/$2" exit 1 fi if [[ "$result" != *'host based authentication rejected'* ]]; then echo "ERROR: expected HBA rejection for $1/$2: $result" exit 1 fi echo "OK: HBA rejects $1/$2 via $host" } check_allowed hba_default_db hba_allowed check_rejected hba_default_db hba_denied check_rejected hba_unknown_db hba_allowed check_allowed hba_sameuser hba_sameuser check_rejected hba_sameuser hba_otheruser odyssey-1.6.0-rc1/test/functional/tests/hba/unix.conf000066400000000000000000000000701526064202200225430ustar00rootroot00000000000000include "/tests/hba/common.conf" listen { port 6432 } odyssey-1.6.0-rc1/test/functional/tests/invalid_log_file/000077500000000000000000000000001526064202200234505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/invalid_log_file/config.conf000066400000000000000000000013101526064202200255570ustar00rootroot00000000000000 storage "postgres_server" { type "remote" host "localhost" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "session" client_fwd_error yes } } unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/hgdfgf/juyhgfd/invalid.log" log_to_stdout no daemonize no locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes stats_interval 60 pid_file "/var/run/odyssey.pid" listen { host "*" port 6432 } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/functional/tests/invalid_log_file/runner.sh000077500000000000000000000003101526064202200253120ustar00rootroot00000000000000#!/bin/bash -x set -ex if ! /usr/bin/odyssey /tests/invalid_log_file/config.conf | grep -q "failed to open log file "; then echo "ERROR: can't find log open error in stdout" exit 1 fi ody-stopodyssey-1.6.0-rc1/test/functional/tests/lagpolling/000077500000000000000000000000001526064202200223125ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/lagpolling/lag-conf.conf000066400000000000000000000041171526064202200246520ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session yes log_query yes log_stats yes stats_interval 60 log_file "/var/log/odyssey.log" workers 5 resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5433 watchdog { authentication "none" # makes no sense storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" # 1 for cron & 9 for console db queries pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM pg_last_xact_replay_timestamp()))" watchdog_lag_interval 1 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres" { user "user1" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes catchup_timeout 5 catchup_checks 10 client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/lagpolling/runner.sh000077500000000000000000000044261526064202200241700ustar00rootroot00000000000000#!/bin/bash -x set -eux reset_replica_lag() { psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "ALTER SYSTEM RESET recovery_min_apply_delay;" || true psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "SELECT pg_reload_conf();" || true } enable_replica_lag() { psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "ALTER SYSTEM SET recovery_min_apply_delay = '15s'" psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "SELECT pg_reload_conf();" trap reset_replica_lag EXIT } psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'DROP TABLE IF EXISTS wal_bump_data' 2>&1 || { exit 1 } psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'CREATE TABLE IF NOT EXISTS wal_bump_data(num int)' 2>&1 || { exit 1 } psql 'host=localhost port=5433 user=postgres dbname=postgres' -c 'SELECT TRUNC(EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()))' enable_replica_lag psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'INSERT INTO wal_bump_data VALUES(42)' 2>&1 || { exit 1 } psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'INSERT INTO wal_bump_data VALUES(43)' 2>&1 || { exit 1 } /usr/bin/odyssey /tests/lagpolling/lag-conf.conf timeout 5 bash -c ' until pg_isready -h localhost -p 6432 -U user1 -d postgres; do echo "Wait for odyssey..." sleep 0.1 done ' || { echo "Failed to start odyssey" sleep 1 cat /var/log/odyssey.log exit 1 } for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' || { sleep 1 cat /var/log/odyssey.log exit 1 } done sleep 6 psql 'host=localhost port=5433 user=postgres dbname=postgres' -c 'SELECT TRUNC(EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()))' for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' && exit 1 done sed -i 's/catchup_timeout 5/catchup_timeout 20/g' /tests/lagpolling/lag-conf.conf PGPASSWORD=lolol psql -h localhost -p6432 -dconsole -Uuser1 -c 'reload' psql 'host=localhost port=5433 user=postgres dbname=postgres' -c 'SELECT TRUNC(EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()))' for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' || exit 1 done ody-stop odyssey-1.6.0-rc1/test/functional/tests/lagpolling_port/000077500000000000000000000000001526064202200233565ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/lagpolling_port/lag-conf.conf000066400000000000000000000041651526064202200257210ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session yes log_query yes log_stats yes stats_interval 60 log_file "/var/log/odyssey.log" workers 5 resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 catchup_timeout 5 } listen { host "*" port 7432 backlog 128 catchup_timeout 25 } storage "postgres_server" { type "remote" host "localhost" port 5433 watchdog { authentication "none" # makes no sense storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" # 1 for cron & 9 for console db queries pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM pg_last_xact_replay_timestamp()))" watchdog_lag_interval 1 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres" { user "user1" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/lagpolling_port/runner.sh000077500000000000000000000035031526064202200252270ustar00rootroot00000000000000#!/bin/bash -x set -eux reset_replica_lag() { psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "ALTER SYSTEM RESET recovery_min_apply_delay;" || true psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "SELECT pg_reload_conf();" || true } enable_replica_lag() { psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "ALTER SYSTEM SET recovery_min_apply_delay = '15s'" psql 'host=localhost port=5433 user=postgres dbname=postgres' -c "SELECT pg_reload_conf();" trap reset_replica_lag EXIT } psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'DROP TABLE IF EXISTS wal_bump_data' 2>&1 || { exit 1 } psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'CREATE TABLE IF NOT EXISTS wal_bump_data(num int)' 2>&1 || { exit 1 } psql 'host=localhost port=5433 user=postgres dbname=postgres' -c 'SELECT TRUNC(EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()))' enable_replica_lag psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'INSERT INTO wal_bump_data VALUES(42)' 2>&1 || { exit 1 } psql 'host=localhost port=5432 user=postgres dbname=postgres' -c 'INSERT INTO wal_bump_data VALUES(43)' 2>&1 || { exit 1 } /usr/bin/odyssey /tests/lagpolling_port/lag-conf.conf timeout 5 bash -c ' until pg_isready -h localhost -p 6432 -U user1 -d postgres; do echo "Wait for odyssey..." sleep 0.1 done ' sleep 6 psql 'host=localhost port=5433 user=postgres dbname=postgres' -c 'SELECT TRUNC(EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()))' for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' && { cat /var/log/odyssey.log exit 1 } done for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p7432 -dpostgres -Uuser1 -c 'select 3' || { cat /var/log/odyssey.log exit 1 } done ody-stop odyssey-1.6.0-rc1/test/functional/tests/ldap/000077500000000000000000000000001526064202200211025ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/ldap/odyssey.conf000066400000000000000000000057111526064202200234540ustar00rootroot00000000000000 storage "postgres_server" { type "remote" host "localhost" port 5432 } # ldapserver=localhost ldapbinddn="cn=admin,dc=example,dc=org" ldapbasedn="dc=example,dc=org" ldapbindpasswd="admin" ldapsearchfilter="(uid=$username)" ldap_endpoint "ldap1" { ldapscheme "ldap" ldapbasedn "dc=example,dc=org" ldapbinddn "cn=admin,dc=example,dc=org" ldapbindpasswd "admin" ldapsearchattribute "gecos" ldapserver "192.168.233.16" ldapport 389 } ldap_endpoint "ldap_storage_creds" { ldapscheme "ldap" ldapbasedn "dc=example,dc=org" ldapbinddn "cn=admin,dc=example,dc=org" ldapbindpasswd "admin" ldapsearchfilter "(memberOf=cn=localhost,ou=groups,dc=example,dc=org)" ldapsearchattribute "gecos" ldapserver "192.168.233.16" ldapport 389 } database default { user default { authentication "clear_text" storage "postgres_server" pool "session" pool_size 10 ldap_pool_size 10 ldap_pool_timeout 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap_storage_creds" ldap_storage_credentials_attr "memberof" ldap_storage_credentials "group_ro" { ldap_storage_username "ldap_readonly" ldap_storage_password "ldap_pass_readonly" } ldap_storage_credentials "group_rw" { ldap_storage_username "ldap_rw" ldap_storage_password "ldap_pass_rw" } quantiles "0.99,0.95,0.5" client_max 107 } } database "ldap_db" { user "user1" { authentication "clear_text" storage "postgres_server" pool "session" pool_size 10 ldap_pool_size 10 ldap_pool_timeout 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" quantiles "0.99,0.95,0.5" client_max 107 } user default { authentication "clear_text" storage "postgres_server" pool "session" pool_size 10 ldap_pool_size 10 ldap_pool_timeout 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes reserve_session_server_connection no server_lifetime 3600 log_debug no ldap_endpoint_name "ldap1" quantiles "0.99,0.95,0.5" client_max 10 } } unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_debug no log_config yes log_session yes log_query no log_stats no daemonize yes locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes stats_interval 60 pid_file "/var/run/odyssey.pid" listen { host "*" port 6432 } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/ldap/runner.sh000077500000000000000000000050631526064202200227560ustar00rootroot00000000000000#!/bin/bash -x # reentrant test ldapdelete -x -H ldap://192.168.233.16 -D "cn=admin,dc=example,dc=org" -wadmin "uid=user1,dc=example,dc=org" || true ldapdelete -x -H ldap://192.168.233.16 -D "cn=admin,dc=example,dc=org" -wadmin "uid=user3,dc=example,dc=org" || true ldapdelete -x -H ldap://192.168.233.16 -D "cn=admin,dc=example,dc=org" -wadmin "uid=user4,dc=example,dc=org" || true ldapadd -x -H ldap://192.168.233.16 -D "cn=admin,dc=example,dc=org" -wadmin -f /tests/ldap/usr1.ldif # wait for ldap server to do smt sleep 1 ldapadd -x -H ldap://192.168.233.16 -D "cn=admin,dc=example,dc=org" -wadmin -f /tests/ldap/usr3.ldif # wait for ldap server to do smt sleep 1 ldapadd -x -H ldap://192.168.233.16 -D "cn=admin,dc=example,dc=org" -wadmin -f /tests/ldap/usr4.ldif # wait for ldap server to do smt sleep 1 /usr/bin/odyssey /tests/ldap/odyssey.conf sleep 1 PGPASSWORD=lolol psql -h localhost -p 6432 -U user1 -c "select 1" ldap_db >/dev/null 2>&1 || { echo "error: failed to successfully auth with correct password" ody-stop cat /var/log/odyssey.log exit 1 } PGPASSWORD=notlolol psql -h localhost -p 6432 -U user1 -c "select 1" ldap_db >/dev/null 2>&1 && { echo "error: successfully auth with incorrect password" ody-stop cat /var/log/odyssey.log exit 1 } PGPASSWORD=default psql -h localhost -p 6432 -U user3 -c "select 1" ldap_db >/dev/null 2>&1 && { echo "error: failed to successfully auth with correct password as default user" ody-stop cat /var/log/odyssey.log exit 1 } PGPASSWORD=notdefault psql -h localhost -p 6432 -U user3 -c "select 1" ldap_db >/dev/null 2>&1 && { echo "error: successfully auth with incorrect password as default user" ody-stop cat /var/log/odyssey.log exit 1 } PGPASSWORD=default psql -h localhost -p 6432 -U user4 -c "select current_user" ldap_db1 2>/dev/null | grep ldap_readonly | wc -l | grep -q '1' || { echo "error: failed to successfully auth with correct password and correct db" ody-stop cat /var/log/odyssey.log exit 1 } PGPASSWORD=default psql -h localhost -p 6432 -U user4 -c "select current_user" ldap_db2 2>/dev/null | grep ldap_rw | wc -l | grep -q '1' || { echo "error: failed to successfully auth with correct password and correct db" ody-stop cat /var/log/odyssey.log exit 1 } PGPASSWORD=notdefault psql -h localhost -p 6432 -U user4 -c "select 1" ldap_db1 >/dev/null 2>&1 && { echo "error: successfully auth with incorrect password" ody-stop cat /var/log/odyssey.log exit 1 } ody-stop || { cat /var/log/odyssey.log exit 1 } odyssey-1.6.0-rc1/test/functional/tests/ldap/usr1.ldif000066400000000000000000000004661526064202200226420ustar00rootroot00000000000000dn: uid=user1,dc=example,dc=org objectClass: top objectClass: account objectClass: posixAccount objectClass: shadowAccount cn: user1 uid: user1 uidNumber: 16859 gidNumber: 100 homeDirectory: /home/user1 loginShell: /bin/bash gecos: user1 userPassword: lolol shadowLastChange: 0 shadowMax: 0 shadowWarning: 0 odyssey-1.6.0-rc1/test/functional/tests/ldap/usr3.ldif000066400000000000000000000004701526064202200226370ustar00rootroot00000000000000dn: uid=user3,dc=example,dc=org objectClass: top objectClass: account objectClass: posixAccount objectClass: shadowAccount cn: user3 uid: user3 uidNumber: 16859 gidNumber: 100 homeDirectory: /home/user3 loginShell: /bin/bash gecos: user3 userPassword: default shadowLastChange: 0 shadowMax: 0 shadowWarning: 0 odyssey-1.6.0-rc1/test/functional/tests/ldap/usr4.ldif000066400000000000000000000007631526064202200226450ustar00rootroot00000000000000dn: uid=user4,dc=example,dc=org objectClass: top objectClass: account objectClass: posixAccount objectClass: shadowAccount cn: user4 uid: user4 memberof: cn=localhost,ou=groups,dc=example,dc=org memberof: cn=localhost_ldap_db1_group_ro,ou=groups,dc=example,dc=org memberof: cn=localhost_ldap_db2_group_rw,ou=groups,dc=example,dc=org uidNumber: 16860 gidNumber: 101 homeDirectory: /home/user4 loginShell: /bin/bash gecos: user4 userPassword: default shadowLastChange: 0 shadowMax: 0 shadowWarning: 0 odyssey-1.6.0-rc1/test/functional/tests/listen_storage_failover/000077500000000000000000000000001526064202200250735ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/listen_storage_failover/odyssey.conf000066400000000000000000000025631526064202200274470ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_debug yes log_config yes log_session yes log_query yes log_stats no log_file "/var/log/odyssey.log" workers 1 resolvers 1 readahead 8192 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes backend_connect_timeout_ms 2000 storage "live_storage" { type "remote" host "127.0.0.1" port 5432 } storage "dead_storage" { type "remote" host "127.0.0.1" port 19999 } storage "dead_storage2" { type "remote" host "127.0.0.1" port 19998 } listen { host "*" port 6432 backlog 128 storage "dead_storage" storage "live_storage" } listen { host "*" port 7432 backlog 128 storage "live_storage" storage "dead_storage" } listen { host "*" port 8432 backlog 128 storage "dead_storage" storage "dead_storage2" } database "postgres" { user "postgres" { authentication "none" storage "dead_storage" pool "session" pool_size 10 pool_timeout 5 client_fwd_error yes server_lifetime 3600 log_debug yes } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/listen_storage_failover/runner.sh000077500000000000000000000025311526064202200267440ustar00rootroot00000000000000#!/bin/bash set -ex /usr/bin/odyssey /tests/listen_storage_failover/odyssey.conf timeout 5 bash -c ' until pg_isready -h localhost -p 6432 -U postgres -d postgres; do echo "Wait for odyssey on 6432..." sleep 0.1 done ' timeout 5 bash -c ' until pg_isready -h localhost -p 7432 -U postgres -d postgres; do echo "Wait for odyssey on 7432..." sleep 0.1 done ' timeout 5 bash -c ' until pg_isready -h localhost -p 8432 -U postgres -d postgres; do echo "Wait for odyssey on 8432..." sleep 0.1 done ' psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'SELECT 1' || { cat /var/log/odyssey.log echo "FAIL: port 6432 should succeed via fallback to live_storage" exit 1 } psql 'host=localhost port=7432 user=postgres dbname=postgres' -c 'SELECT 1' || { cat /var/log/odyssey.log echo "FAIL: port 7432 should succeed connecting to live_storage directly" exit 1 } psql 'host=localhost port=8432 user=postgres dbname=postgres' -c 'SELECT 1' 2>/dev/null && { cat /var/log/odyssey.log echo "FAIL: port 8432 should fail when all storages are dead" exit 1 } for i in $(seq 1 5); do psql 'host=localhost port=6432 user=postgres dbname=postgres' -c "SELECT $i" || { cat /var/log/odyssey.log echo "FAIL: repeated connection $i through failover port should work" exit 1 } done ody-stop odyssey-1.6.0-rc1/test/functional/tests/min_pool_size/000077500000000000000000000000001526064202200230305ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/min_pool_size/config.conf000066400000000000000000000013051526064202200251430ustar00rootroot00000000000000storage "postgres_server" { host "[127.0.0.1]:5432" type "remote" } storage "console" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" min_pool_size 5 pool_timeout 1000 pool_size 5 pool_ttl 5 } } database "console" { user "console" { authentication "none" storage "console" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/min_pool_size/runner.sh000077500000000000000000000026651526064202200247110ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/min_pool_size/config.conf sleep 1 # create some connections for od_frontend_setup_params pgbench 'host=localhost port=6432 user=postgres dbname=postgres' --select-only --progress 1 --no-vacuum -T 5 -j 1 -c 5 psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | sort > /tmp/setup-connections # wait for this connections to expire by ttl and new connections created sleep 11 psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | sort > /tmp/newly-preallocated if diff /tmp/setup-connections /tmp/newly-preallocated; then echo "setup connections must be differ from newly preallocated" cat /tmp/setup-connections cat /tmp/newly-preallocated exit 1 fi servers_count=`cat /tmp/newly-preallocated | wc -l` if [ $servers_count != 5 ]; then echo 'there must be 5 newly preallocated servers' cat /var/log/odyssey.log exit 1 fi # use this connections pgbench 'host=localhost port=6432 user=postgres dbname=postgres' --select-only --progress 1 --no-vacuum -T 5 -j 1 -c 5 # make sure that no new connections was created psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | sort > /tmp/post-usage diff /tmp/newly-preallocated /tmp/post-usage || { echo "server lists are different" cat /tmp/newly-preallocated cat /tmp/post-usage cat /var/log/odyssey.log exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/min_pool_size_reload/000077500000000000000000000000001526064202200243565ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/min_pool_size_reload/config.conf000066400000000000000000000013071526064202200264730ustar00rootroot00000000000000storage "postgres_server" { host "[127.0.0.1]:5432" type "remote" } storage "console" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" min_pool_size 5 pool_timeout 1000 pool_size 50 pool_ttl 50 } } database "console" { user "console" { authentication "none" storage "console" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/min_pool_size_reload/runner.sh000077500000000000000000000017331526064202200262320ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/min_pool_size_reload/config.conf sleep 1 # wait for min_pool size connections to be created sleep 6 psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' servers_count=`psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | wc -l` if [ $servers_count != 5 ]; then echo 'there must be 5 newly preallocated servers' cat /var/log/odyssey.log exit 1 fi # ensure min_pool_size are reloaded correctly sed -i 's/min_pool_size\ 5/min_pool_size\ 10/g' /tests/min_pool_size_reload/config.conf kill -sHUP $(pidof odyssey) sleep 1 # ensure new connections created sleep 11 psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' servers_count=`psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | wc -l` if [ $servers_count != 10 ]; then echo "min_pool_size was not reloaded correctly" exit 1 fi ody-stop odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/000077500000000000000000000000001526064202200230315ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/config.conf000066400000000000000000000014501526064202200251450ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "postgres" { user "postgres" { authentication "none" pool_routing "internal" storage "postgres_server" pool "session" } } database "auth_query_db" { user "auth_query_user_scram_sha_256" { authentication "scram-sha-256" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage "postgres_server" pool "session" } } odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/npgsql.sln000066400000000000000000000024561526064202200250620ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4BD0CFE8-9294-44BE-A00A-40E0F5638845}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NpgsqlOdysseyScram.Console", "src\NpgsqlOdysseyScram.Console\NpgsqlOdysseyScram.Console.csproj", "{D8B797B2-971F-4B28-B51B-ACCAB7B09789}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D8B797B2-971F-4B28-B51B-ACCAB7B09789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D8B797B2-971F-4B28-B51B-ACCAB7B09789}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8B797B2-971F-4B28-B51B-ACCAB7B09789}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8B797B2-971F-4B28-B51B-ACCAB7B09789}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {D8B797B2-971F-4B28-B51B-ACCAB7B09789} = {4BD0CFE8-9294-44BE-A00A-40E0F5638845} EndGlobalSection EndGlobal odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/runner.sh000077500000000000000000000010011526064202200246710ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/npgsql_compat/config.conf /tests/npgsql_compat/NpgsqlOdysseyScram.Console || { echo "ERROR: npgsql-compat tests failed" for i in /asan-output*; do cat $i || true done sleep 1 cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop || { for i in /asan-output*; do cat $i || true done sleep 1 cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } exit 0 odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/src/000077500000000000000000000000001526064202200236205ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console/000077500000000000000000000000001526064202200310335ustar00rootroot00000000000000NpgsqlOdysseyScram.Console.csproj000066400000000000000000000005141526064202200374110ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console Exe net10.0 enable enable odyssey-1.6.0-rc1/test/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console/Program.cs000066400000000000000000000020671526064202200327760ustar00rootroot00000000000000using Npgsql; namespace NpgsqlOdysseyScram.Console { class EntryPoint { private static async Task TestScramAuth() { // npgsql sends 'eSws' when no channel binding, odyssey must be ready const string connectionString = "Pooling=false;Host=localhost;Port=6432;Username=auth_query_user_scram_sha_256;Password=passwd;Database=auth_query_db;Keepalive=5;Include Error Detail=true;SSL Mode=disable;"; var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); await using var dataSource = dataSourceBuilder.Build(); await using var connection = await dataSource.OpenConnectionAsync(); using var command = new NpgsqlCommand("select 1+2;", connection); await using var reader = await command.ExecuteReaderAsync(); await reader.ReadAsync(); if (reader.GetInt32(0) != 3) { throw new Exception("Unexpected result"); } } public static async Task Main() { await TestScramAuth(); } } }odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/000077500000000000000000000000001526064202200244175ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/go.mod000066400000000000000000000004611526064202200255260ustar00rootroot00000000000000module pkg go 1.25.0 require ( github.com/jackc/pgx/v5 v5.9.2 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 ) require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect golang.org/x/text v0.31.0 // indirect ) odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/go.sum000066400000000000000000000061041526064202200255530ustar00rootroot00000000000000filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/000077500000000000000000000000001526064202200252005ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/client-server.go000066400000000000000000000132611526064202200303140ustar00rootroot00000000000000package main import ( "context" "fmt" "math/rand" "sync" "syscall" "time" ) func usrReadResultWhilesigusr2Test( ctx context.Context, ) error { err := ensurePostgresqlRunning(ctx) if err != nil { return err } if err := ensureOdysseyRunning(ctx); err != nil { return err } db, err := getConn(ctx, databaseName, 1) if err != nil { return err } defer db.Close() t, err := db.Beginx() if err != nil { return err } ch := make(chan error, 1) go func(chan error) { rows, err := t.Queryx("select pg_sleep(10)") rows.Close() ch <- err }(ch) if _, err := signalToProc(syscall.SIGUSR2, "odyssey"); err != nil { return err } err, ok := <-ch close(ch) if err != nil || !ok { return fmt.Errorf("connection closed or reset") } err = t.Commit() if err != nil { return fmt.Errorf("commit failed: %w", err) } return nil } func select42(ctx context.Context, ch chan error, wg *sync.WaitGroup) { defer wg.Done() db, err := getConn(ctx, databaseName, 1) if err != nil { ch <- err fmt.Println(err) return } defer db.Close() rows, err := db.Query("Select 42") if err != nil { ch <- err fmt.Println(err) return } rows.Close() fmt.Printf("select 42 OK\n") } func selectSleepNoWait(ctx context.Context, i int) error { db, err := getConn(ctx, databaseName, 1) if err != nil { return err } defer db.Close() qry := fmt.Sprintf("SELECT pg_sleep(%d)", i) fmt.Printf("selectSleep: doing query %s\n", qry) _ = db.QueryRowContext(ctx, qry) fmt.Print("select sleep OK\n") return err } func selectSleep(ctx context.Context, i int, ch chan error, wg *sync.WaitGroup, before_restart bool) { defer wg.Done() db, err := getConn(ctx, databaseName, 1) if err != nil { if before_restart { err = fmt.Errorf("before restart coroutine: %w", err) } else { err = fmt.Errorf("after restart coroutine: %w", err) } ch <- err fmt.Println(err) return } defer db.Close() qry := fmt.Sprintf("SELECT pg_sleep(%d)", i) fmt.Printf("selectSleep: doing query %s\n", qry) r, err := db.QueryContext(ctx, qry) if err != nil { if before_restart { err = fmt.Errorf("before restart coroutine failed %w", err) } else { err = fmt.Errorf("after restart coroutine failed %w", err) } fmt.Printf("sleep coroutine fail time: %s\n", time.Now().Format(time.RFC3339Nano)) ch <- err return } else { for r.Next() { r.Scan(struct { }{}) } r.Close() } ch <- nil fmt.Print("select sleep OK\n") } const ( sleepInterval = 5 maxCoroutineFailOk = 4 ) func waitOnChan(ch chan error, maxOK int) error { failedCnt := 0 for { select { case err, ok := <-ch: if !ok { if failedCnt > maxOK { return fmt.Errorf("too many coroutines failed") } return nil } if err != nil { fmt.Println(err) failedCnt++ } } } } func onlineRestartTest(ctx context.Context) error { err := ensurePostgresqlRunning(ctx) if err != nil { return err } if err := stopOdyssey(ctx); err != nil { return err } if err := ensureOdysseyRunning(ctx); err != nil { return err } coroutineSleepCnt := 5 repeatCnt := 4 for j := range repeatCnt { fmt.Printf("Iter %d\n", j) ch := make(chan error, coroutineSleepCnt*5) wg := sync.WaitGroup{} { for range coroutineSleepCnt { wg.Add(1) go selectSleep(ctx, sleepInterval, ch, &wg, true) } for range coroutineSleepCnt { wg.Add(1) go func() { time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) select42(ctx, ch, &wg) }() } // to make sure previous select was in old ody time.Sleep(1 * time.Second) if err = restartOdyssey(ctx); err != nil { return err } for i := 0; i < coroutineSleepCnt*2; i++ { wg.Add(1) go selectSleep(ctx, sleepInterval, ch, &wg, false) } for range coroutineSleepCnt { wg.Add(1) go func() { time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) select42(ctx, ch, &wg) }() } } wg.Wait() fmt.Println("onlineRestartTest: wait done, closing channel") close(ch) // no single coroutine should fail! if err := waitOnChan(ch, 7); err != nil { fmt.Println(fmt.Errorf("online restart failed %w", err)) return err } time.Sleep(1 * time.Second) fmt.Printf("Iter %d complete\n", j) } if err = stopOdyssey(ctx); err != nil { time.Sleep(time.Second * 1000) return err } return nil } func sigusr2Test( ctx context.Context, ) error { err := ensurePostgresqlRunning(ctx) if err != nil { return err } if err := ensureOdysseyRunning(ctx); err != nil { return err } coroutineSleepCnt := 10 ch := make(chan error, coroutineSleepCnt+1) wg := sync.WaitGroup{} { for range coroutineSleepCnt { wg.Add(1) go selectSleep(ctx, sleepInterval, ch, &wg, true) } time.Sleep(1 * time.Second) wg.Add(1) go sigusr2Odyssey(ctx, ch, &wg) } wg.Wait() fmt.Println("sigusr2Test: wait done, closing channel") close(ch) if err := waitOnChan(ch, 7); err != nil { fmt.Println(fmt.Errorf("sigusr2 failed %w", err)) return err } if err := stopOdyssey(ctx); err != nil { return err } return nil } func odyClientServerInteractionsTestSet(ctx context.Context) error { if err := usrReadResultWhilesigusr2Test(ctx); err != nil { err = fmt.Errorf("usrReadResultWhilesigusr2: %w", err) fmt.Println(err) return err } logTestDone("usrReadResultWhilesigusr2Test") if err := onlineRestartTest(ctx); err != nil { err = fmt.Errorf("online restart error %w", err) fmt.Println(err) return err } logTestDone("onlineRestartTest") if err := sigusr2Test(ctx); err != nil { err = fmt.Errorf("sigusr2 error %w", err) fmt.Println(err) return err } logTestDone("sigusr2Test") fmt.Println("odyClientServerInteractionsTestSet: Ok") return nil } odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/cores.go000066400000000000000000000032271526064202200266460ustar00rootroot00000000000000package main import ( _ "bufio" _ "bytes" "context" "fmt" "io/ioutil" _ "os" "os/exec" "strconv" "sync" "syscall" "time" ) const benchTimeSec = 10 const timeSleep = 5 const procName = "odyssey" const signal = syscall.SIGTERM const testCount = 10 func bunchProcess(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() _, err := exec.CommandContext(ctx, "pgbench", "--builtin", "select-only", "-c", "40", "-T", strconv.Itoa(benchTimeSec), "-j", "20", "-n", "-h", "localhost", "-p", "6432", "-U", "postgres", "postgres", "-P", "1").Output() if err != nil { fmt.Printf("pgbench error: %v\n", err) } } func SigTermAfterHighLoad(ctx context.Context) error { for i := range testCount { fmt.Printf("Test number: %d\n", i+1) if err := ensurePostgresqlRunning(ctx); err != nil { return err } if err := ensureOdysseyRunning(ctx); err != nil { return err } var wg sync.WaitGroup wg.Add(1) go bunchProcess(ctx, &wg) time.Sleep(timeSleep * time.Second) if _, err := signalToProc(signal, procName); err != nil { fmt.Println(err.Error()) } wg.Wait() } files, err := ioutil.ReadDir("/var/cores") if err != nil { fmt.Println(err) } countCores := len(files) coresPercent := (float64(countCores) / float64(testCount)) * 100 fmt.Printf("Cores count: %d out of %d (%.2f %%)\n", countCores, testCount, coresPercent) return nil } func odyCoresTestSet(ctx context.Context) error { if err := SigTermAfterHighLoad(ctx); err != nil { err = fmt.Errorf("odyCoresTestSet failed: %w", err) fmt.Println(err) return err } logTestDone("SigTermAfterHighLoad") fmt.Println("odyCoresTestSet: Ok") return nil } odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/main.go000066400000000000000000000005451526064202200264570ustar00rootroot00000000000000package main import ( "context" "fmt" "os" _ "github.com/lib/pq" ) func main() { ctx := context.Background() for _, f := range []func(ctx2 context.Context) error{ odyClientServerInteractionsTestSet, odyPkgSyncTestSet, odyShowErrsTestSet, odyCoresTestSet, } { err := f(ctx) if err != nil { os.Exit(3) } } fmt.Println("done") } odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/pkg-sync.go000066400000000000000000000021371526064202200272650ustar00rootroot00000000000000package main import ( "context" "fmt" "os" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgproto3" ) func syncPackets(ctx context.Context) error { err := ensurePostgresqlRunning(ctx) if err != nil { return err } err = ensureOdysseyRunning(ctx) if err != nil { return err } conn, err := pgx.Connect(ctx, "host=localhost port=6432 user=postgres database=postgres") if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Unable to connection to database: %v\n", err) return err } pgConn := conn.PgConn().Conn() buf := make([]byte, 8192) buf, err = (&pgproto3.Query{String: "select 1"}).Encode(buf) if err != nil { return err } buf[0] = 0x80 buf[1] = 0x80 buf[2] = 0x80 buf[3] = 0x80 _, err = pgConn.Write(buf) if err != nil { return err } p := make([]byte, 8192) _, err = pgConn.Read(p) return OdysseyIsAlive(ctx) } func odyPkgSyncTestSet(ctx context.Context) error { if err := syncPackets(ctx); err != nil { err = fmt.Errorf("package sync error %w", err) fmt.Println(err) return err } logTestDone("syncPackets") fmt.Println("odyPkgSyncTestSet: Ok") return nil } odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/showerrs.go000066400000000000000000000060751526064202200274130ustar00rootroot00000000000000package main import ( "context" "database/sql" "fmt" "time" "github.com/jmoiron/sqlx" ) func getErrs(ctx context.Context, db *sqlx.DB) (map[string]int, error) { qry := `SHOW ERRORS` rows, err := db.QueryContext(ctx, qry) if err != nil { return nil, fmt.Errorf("error while exec query %s: %w", qry, err) } mp := make(map[string]int) parser := func(rows *sql.Rows) error { for rows.Next() { var name string var cnt int err := rows.Scan(&name, &cnt) if err != nil { return err } mp[name] = cnt } return rows.Close() } if err := parser(rows); err != nil { return nil, fmt.Errorf("error while parsing %w", err) } return mp, nil } func showErrors(ctx context.Context) error { // restarting odyssey drops show errs view, but we have change to request show errors to old instance // so we explicitly kill old od if err := stopOdyssey(ctx); err != nil { return err } if err := ensureOdysseyRunning(ctx); err != nil { return err } console := "console" pgConString := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable user=%s", hostname, odyPort, console, username) db, err := sqlx.Open("postgres", pgConString) if err != nil { return err } defer db.Close() if mp, err := getErrs(ctx, db); err != nil { return err } else { for _, name := range []string{ "OD_EATTACH", "OD_EATTACH_TOO_MANY_CONNECTIONS", "OD_ESERVER_CONNECT", "OD_ESERVER_READ", "OD_ESERVER_WRITE", "OD_ECLIENT_WRITE", "OD_ECLIENT_READ", "OD_ESYNC_BROKEN", "OD_ROUTER_ERROR_REPLICATION", "OD_EOOM", "OD_ROUTER_ERROR_TIMEDOUT", "OD_ROUTER_ERROR_LIMIT_ROUTE", "OD_ROUTER_ERROR_LIMIT", "OD_ROUTER_ERROR_NOT_FOUND", "OD_ROUTER_ERROR", } { if mp[name] != 0 { return fmt.Errorf("error jst atfer restart: %s - %d", name, mp[name]) } } } return nil } func showErrorsAfterPgRestart(ctx context.Context) error { // restarting odyssey drops show errs view, but we have change to request show errors to old instance // so we explicitly kill old od if err := stopOdyssey(ctx); err != nil { return err } if err := ensureOdysseyRunning(ctx); err != nil { return err } if err := restartPg(ctx); err != nil { return err } console := "console" pgConString := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable user=%s", hostname, odyPort, console, username) db, err := sqlx.Open("postgres", pgConString) if err != nil { return err } defer db.Close() cor_cnt := 10 for range cor_cnt { go selectSleepNoWait(ctx, 10) } time.Sleep(2 * time.Second) /* TODO: drop this test or make it work */ if _, err := getErrs(ctx, db); err != nil { return err } return nil } func odyShowErrsTestSet(ctx context.Context) error { if err := showErrors(ctx); err != nil { err = fmt.Errorf("show errors failed: %w", err) fmt.Println(err) return err } logTestDone("showErrors") if err := showErrorsAfterPgRestart(ctx); err != nil { err = fmt.Errorf("show errors failed: %w", err) fmt.Println(err) return err } logTestDone("showErrorsAfterPgRestart") fmt.Println("odyShowErrsTestSet: Ok") return nil } odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/pkg/util.go000066400000000000000000000112331526064202200265040ustar00rootroot00000000000000package main import ( "bytes" "context" "fmt" "io/ioutil" "os" "os/exec" "strconv" "sync" "syscall" "time" "github.com/jmoiron/sqlx" ) const pgCtlcluster = "/usr/lib/postgresql/18/bin/pg_ctl" const restartOdysseyCmd = "/usr/bin/ody-restart" const stopOdysseyCmd = "/usr/bin/ody-stop" const startOdysseyCmd = "/usr/bin/ody-start" func restartPg(ctx context.Context) error { for range 5 { out, err := exec.CommandContext(ctx, pgCtlcluster, "-D", "/var/lib/postgresql/18/main/", "restart").Output() fmt.Printf("pg ctl out: %v\n", out) if err != nil { fmt.Printf("got error: %v\n", err) } // wait for postgres to restart time.Sleep(2 * time.Second) return nil } return fmt.Errorf("error due postgresql restarting") } func ensurePostgresqlRunning(ctx context.Context) error { if err := restartPg(ctx); err != nil { return err } fmt.Print("ensurePostgresqlRunning: OK\n") return nil } func ensureOdysseyRunning(ctx context.Context) error { fmt.Printf("ensuring odyssey is OK or not\n") _, err := exec.CommandContext(ctx, startOdysseyCmd).Output() if err != nil { err = fmt.Errorf("error due odyssey restarting %w", err) fmt.Println(err) return err } err = waitOnOdysseyAlive(ctx, time.Second*3) if err != nil { return err } fmt.Print("odyssey running: OK\n") return nil } func restartOdyssey(ctx context.Context, ) error { output, err := exec.CommandContext(ctx, restartOdysseyCmd).Output() if err != nil { err = fmt.Errorf("error due odyssey restarting %w", err) fmt.Println(err) fmt.Println(string(output)) return err } fmt.Print("command restart odyssey executed\n") fmt.Print("restart odyssey: OK\n") return nil } func stopOdyssey(ctx context.Context) error { output, err := exec.CommandContext(ctx, stopOdysseyCmd).Output() if err != nil { err = fmt.Errorf("error due odyssey stop %w", err) fmt.Println(err) fmt.Println(string(output)) return err } fmt.Print("command stop odyssey executed\n") fmt.Print("stop odyssey: OK\n") return nil } func logTestDone(name string) { fmt.Printf("==== done test %s ====\n", name) } func pidNyName(procName string) (int, error) { d, err := ioutil.ReadFile(fmt.Sprintf("/var/run/%s.pid", procName)) if err != nil { return -1, err } pid, err := strconv.Atoi(string(bytes.TrimSpace(d))) return pid, nil } func signalToProc(sig syscall.Signal, procName string) (*os.Process, error) { pid, err := pidNyName(procName) if err != nil { err = fmt.Errorf("error due sending signal %s to process %s %w", sig.String(), procName, err) fmt.Println(err) return nil, err } fmt.Printf("signalToProc: %v to %s using pid %d\n", sig, procName, pid) p, err := os.FindProcess(pid) if err != nil { err = fmt.Errorf("error due sending signal %s to process %s %w", sig.String(), procName, err) fmt.Println(err) return p, err } err = p.Signal(sig) if err != nil { err = fmt.Errorf("error due sending signal %s to process %s %w", sig.String(), procName, err) fmt.Println(err) return p, err } return p, nil } func sigusr2Odyssey(ctx context.Context, ch chan error, wg *sync.WaitGroup, ) { defer wg.Done() _, err := signalToProc(syscall.SIGUSR2, "odyssey") if err != nil { ch <- err return } ch <- nil fmt.Print("odyssey signalled: OK\n") } func getConn(ctx context.Context, dbname string, retryCnt int) (*sqlx.DB, error) { pgConString := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable user=%s", hostname, odyPort, dbname, username) for range retryCnt { db, err := sqlx.ConnectContext(ctx, "postgres", pgConString) if err != nil { err = fmt.Errorf("error while connecting to postgresql: %w", err) fmt.Println(err) continue } return db, nil } return nil, fmt.Errorf("failed to get database connection") } const ( hostname = "localhost" hostPort = 5432 odyPort = 6432 username = "postgres" password = "" databaseName = "postgres" ) func OdysseyIsAlive(ctx context.Context) error { db, err := getConn(ctx, databaseName, 2) if err != nil { return err } defer db.Close() qry := fmt.Sprintf("SELECT 42") fmt.Print("OdysseyIsAlive: doing select 42\n") r := db.QueryRowContext(ctx, qry) var i int if err := r.Scan(&i); err == nil { fmt.Println(fmt.Sprintf("selected value %d", i)) } else { fmt.Println(fmt.Errorf("select 42 failed %w", err)) } return err } func waitOnOdysseyAlive(ctx context.Context, timeout time.Duration) error { for OdysseyIsAlive(ctx) != nil { timeout -= time.Second if timeout < 0 { return fmt.Errorf("timeout expired") } time.Sleep(time.Second) fmt.Printf("waiting for od up: remamining time %d\n", timeout/time.Second) } if timeout < 0 { return fmt.Errorf("timeout expired") } return nil } odyssey-1.6.0-rc1/test/functional/tests/ody_integration_test/runner.sh000077500000000000000000000001371526064202200262700ustar00rootroot00000000000000#!/usr/bin/env bash set -ex /tests/ody_integration_test/ody_integration_test sleep 5 ody-stopodyssey-1.6.0-rc1/test/functional/tests/online-restart/000077500000000000000000000000001526064202200231305ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/online-restart/odyssey.conf000066400000000000000000000010761526064202200255020ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config no log_debug no log_session no log_stats no log_query no graceful_shutdown_timeout_ms 5000 conn_drop_options { drop_enabled no } listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } odyssey-1.6.0-rc1/test/functional/tests/online-restart/runner.sh000077500000000000000000000062031526064202200250010ustar00rootroot00000000000000#!/usr/bin/env bash set -eux test_no_restart_on_fail() { /usr/bin/odyssey /tests/online-restart/odyssey.conf sleep 2 # broken config echo "some trash" > /tests/online-restart/odyssey.conf before_restart_pid=$(pidof odyssey) kill -sUSR2 $(pidof odyssey) sleep 5 after_restart_pid=$(pidof odyssey) ps aux | grep odyssey ody_pids_count=$(ps aux | grep '[o]dyssey' | wc -l) if [ "$ody_pids_count" != 1 ]; then echo "too many odyssey instances (" $ody_pids_count ")" cat /var/log/odyssey.log exit 1 fi if [ "$before_restart_pid" != "$after_restart_pid" ]; then echo "odyssey restarted: much fail!" cat /var/log/odyssey.log exit 1 fi ody-stop } test_several_signals() { /usr/bin/odyssey /tests/online-restart/odyssey.conf sleep 2 psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_sleep(5)' 2>&1 > /tests/online-restart/result & sleep 0.5 before_restart_pid=$(pidof odyssey) kill -sUSR2 "$before_restart_pid" || true kill -sUSR2 "$before_restart_pid" || true kill -sUSR2 "$before_restart_pid" || true kill -sUSR2 "$before_restart_pid" || true kill -sUSR2 "$before_restart_pid" || true wait -n || { echo "psql failed" cat /tests/online-restart/result exit 1 } sleep 1 ps aux | grep odyssey ody_pids_count=$(ps aux | grep '[o]dyssey' | wc -l) if [ "$ody_pids_count" != 1 ]; then echo "too many odyssey instances (" $ody_pids_count ")" cat /var/log/odyssey.log exit 1 fi after_restart_pid=$(pidof odyssey) if [ "$before_restart_pid" == "$after_restart_pid" ]; then echo "odyssey didn't restarted" cat /var/log/odyssey.log exit 1 fi ody-stop } test_signal_new_instance_before_parent_die() { /usr/bin/odyssey /tests/online-restart/odyssey.conf sleep 2 psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_sleep(5)' 2>&1 > /tests/online-restart/result & sleep 0.5 before_restart_pid=$(pidof odyssey) kill -sUSR2 $before_restart_pid sleep 1 ps aux | grep '[o]dyssey' after_restart_pid=$(ps aux | grep '[o]dyssey' | grep -v $before_restart_pid | awk '{print $2}') kill -sUSR2 $after_restart_pid || { echo 'seems like new instance failed' cat /var/log/odyssey.log exit 1 } wait -n || { echo "psql failed" cat /tests/online-restart/result exit 1 } sleep 1 ody_pids_count=$(ps aux | grep '[o]dyssey' | wc -l) if [ "$ody_pids_count" != 1 ]; then echo "too many odyssey instances (" $ody_pids_count ")" cat /var/log/odyssey.log exit 1 fi after_restart_pid=$(pidof odyssey) if [ "$before_restart_pid" == "$after_restart_pid" ]; then echo "odyssey didn't restarted" cat /var/log/odyssey.log exit 1 fi ody-stop } # test_several_signals # echo "" > /var/log/odyssey.log # sleep 1 test_signal_new_instance_before_parent_die # echo "" > /var/log/odyssey.log # sleep 1 # test_no_restart_on_fail odyssey-1.6.0-rc1/test/functional/tests/online-restart/sleep.sql000066400000000000000000000000241526064202200247550ustar00rootroot00000000000000select pg_sleep(0.5)odyssey-1.6.0-rc1/test/functional/tests/pause-resume/000077500000000000000000000000001526064202200225755ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pause-resume/runner.sh000077500000000000000000000032341526064202200244470ustar00rootroot00000000000000#!/bin/bash set -uex check_session() { /usr/bin/odyssey /tests/pause-resume/session.conf sleep 1 psql -h localhost -p 6432 -c 'select pg_sleep(5)' -U postgres -d postgres 2>&1 & sleep 1 psql -h localhost -p 6432 -c 'pause' -U console -d console sleep 1 wait -n || { echo "long query must complete even if pause enabled" exit 1 } if psql -h localhost -p 6432 -c 'select 1' -U postgres -d postgres; then echo "new queries must fail until resume executed" exit 1 fi psql -h localhost -p 6432 -c 'resume' -U console -d console sleep 1 psql -h localhost -p 6432 -c 'select 1' -U postgres -d postgres || { echo "after resume queries must work correctly" exit 1 } ody-stop } check_transaction() { /usr/bin/odyssey /tests/pause-resume/transaction.conf sleep 1 pgbench 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -j 2 -c 10 --select-only --no-vacuum --progress 1 -T 15 & sleep 1 psql -h localhost -p 6432 -c 'pause' -U console -d console sleep 6 timeout 1s psql -h localhost -p 6432 -c 'select 1' -U postgres -d postgres if [ $? != 124 ]; then echo "new queries must fail with timeout until resume executed" exit 1 fi psql -h localhost -p 6432 -c 'resume' -U console -d console wait -n || { echo "continious load on transaction should not fall" exit 1 } psql -h localhost -p 6432 -c 'select 1' -U postgres -d postgres || { echo "after resume queries must work correctly" exit 1 } ody-stop } check_session || exit 1 check_transaction || exit 1 odyssey-1.6.0-rc1/test/functional/tests/pause-resume/session.conf000066400000000000000000000012231526064202200251250ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no listen { host "127.0.0.1" port 6432 tls "disable" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/pause-resume/transaction.conf000066400000000000000000000012271526064202200257730ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no listen { host "127.0.0.1" port 6432 tls "disable" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" } } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/pg/000077500000000000000000000000001526064202200205705ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pg/pg_hba-test.conf000066400000000000000000000111361526064202200236360ustar00rootroot00000000000000# PostgreSQL Client Authentication Configuration File # =================================================== # # Refer to the "Client Authentication" section in the PostgreSQL # documentation for a complete description of this file. A short # synopsis follows. # # This file controls: which hosts are allowed to connect, how clients # are authenticated, which PostgreSQL user names they can use, which # databases they can access. Records take one of these forms: # # local DATABASE USER METHOD [OPTIONS] # host DATABASE USER ADDRESS METHOD [OPTIONS] # hostssl DATABASE USER ADDRESS METHOD [OPTIONS] # hostnossl DATABASE USER ADDRESS METHOD [OPTIONS] # # (The uppercase items must be replaced by actual values.) # # The first field is the connection type: "local" is a Unix-domain # socket, "host" is either a plain or SSL-encrypted TCP/IP socket, # "hostssl" is an SSL-encrypted TCP/IP socket, and "hostnossl" is a # plain TCP/IP socket. # # DATABASE can be "all", "sameuser", "samerole", "replication", a # database name, or a comma-separated list thereof. The "all" # keyword does not match "replication". Access to replication # must be enabled in a separate record (see example below). # # USER can be "all", a user name, a group name prefixed with "+", or a # comma-separated list thereof. In both the DATABASE and USER fields # you can also write a file name prefixed with "@" to include names # from a separate file. # # ADDRESS specifies the set of hosts the record matches. It can be a # host name, or it is made up of an IP address and a CIDR mask that is # an integer (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that # specifies the number of significant bits in the mask. A host name # that starts with a dot (.) matches a suffix of the actual host name. # Alternatively, you can write an IP address and netmask in separate # columns to specify the set of hosts. Instead of a CIDR-address, you # can write "samehost" to match any of the server's own IP addresses, # or "samenet" to match any address in any subnet that the server is # directly connected to. # # METHOD can be "trust", "reject", "trust", "password", "scram-sha-256", # "gss", "sspi", "ident", "trust", "pam", "ldap", "radius" or "cert". # Note that "password" sends passwords in clear text; "trust" or # "scram-sha-256" are preferred since they send encrypted passwords. # # OPTIONS are a set of options for the authentication in the format # NAME=VALUE. The available options depend on the different # authentication methods -- refer to the "Client Authentication" # section in the documentation for a list of which options are # available for which authentication methods. # # Database and user names containing spaces, commas, quotes and other # special characters must be quoted. Quoting one of the keywords # "all", "sameuser", "samerole" or "replication" makes the name lose # its special character, and just match a database or username with # that name. # # This file is read on server startup and when the server receives a # SIGHUP signal. If you edit the file on a running system, you have to # SIGHUP the server for the changes to take effect, run "pg_ctl reload", # or execute "SELECT pg_reload_conf()". # # Put your actual configuration here # ---------------------------------- # # If you want to allow non-local connections, you need to add more # "host" records. In that case you will also need to make PostgreSQL # listen on a non-local interface via the listen_addresses # configuration parameter, or via the -i or -h command line switches. # DO NOT DISABLE! # If you change this first entry you will need to make sure that the # database superuser can access the database using some other method. # Noninteractive access to all databases is required during automatic # maintenance (custom daily cronjobs, replication, and similar tasks). # # Database administrative login by Unix domain socket local all postgres trust # TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. local replication all trust host replication all 127.0.0.1/32 trust host replication all ::1/128 trust odyssey-1.6.0-rc1/test/functional/tests/pg/runner.sh000077500000000000000000000000441526064202200224360ustar00rootroot00000000000000# TODO: rewrite this folder exit 0 odyssey-1.6.0-rc1/test/functional/tests/pgoptions/000077500000000000000000000000001526064202200222045ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pgoptions/.gitignore000066400000000000000000000000121526064202200241650ustar00rootroot00000000000000results/* odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/000077500000000000000000000000001526064202200240055ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/invalid_parameter.out000066400000000000000000000000001526064202200302120ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/role.out000066400000000000000000000000461526064202200254770ustar00rootroot00000000000000show role; role ------ zz (1 row) odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/search_path_injection.out000066400000000000000000000004311526064202200310570ustar00rootroot00000000000000show search_path; search_path ----------------------------------- "public;create/**/table/**/zzz()" (1 row) -- from injection select * from public.zzz; ERROR: relation "public.zzz" does not exist LINE 1: select * from public.zzz; ^ odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/search_paths.out000066400000000000000000000001451526064202200272020ustar00rootroot00000000000000show search_path; search_path ------------------------ "$user", public, popis (1 row) odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/several_params.out000066400000000000000000000003531526064202200275430ustar00rootroot00000000000000show role; role ------ zz (1 row) show search_path; search_path ------------- public (1 row) show statement_timeout; statement_timeout ------------------- 1337ms (1 row) show work_mem; work_mem ---------- 765MB (1 row) odyssey-1.6.0-rc1/test/functional/tests/pgoptions/expected/spqrguard.out000066400000000000000000000004531526064202200265500ustar00rootroot00000000000000show spqrguard.prevent_distributed_table_modify; spqrguard.prevent_distributed_table_modify -------------------------------------------- on (1 row) show spqrguard.prevent_reference_table_modify; spqrguard.prevent_reference_table_modify ------------------------------------------ off (1 row) odyssey-1.6.0-rc1/test/functional/tests/pgoptions/odyssey.conf000066400000000000000000000010451526064202200245520ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_session yes smart_search_path_enquoting yes listen { host "127.0.0.1" port 6432 tls "disable" } storage "postgres_server" { type "remote" host "127.0.0.1:5432" } database "postgres" { user "useropt" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" application_name_add_host yes } } odyssey-1.6.0-rc1/test/functional/tests/pgoptions/runner.sh000077500000000000000000000021341526064202200240540ustar00rootroot00000000000000#!/bin/bash set -ex check_file() { local name=$1 local options=$2 export PGOPTIONS=$options cat /tests/pgoptions/sql/$name.sql | psql 'host=localhost port=6432 user=useropt dbname=postgres' --echo-all --quiet > /tests/pgoptions/results/$name.out 2>&1 || { cat /tests/pgoptions/results/$name.out exit 1 } diff /tests/pgoptions/expected/$name.out /tests/pgoptions/results/$name.out || { cat /tests/pgoptions/results/$name.out xxd /tests/pgoptions/results/$name.out exit 1 } } /usr/bin/odyssey /tests/pgoptions/odyssey.conf sleep 1 mkdir -p /tests/pgoptions/results/ # check_file invalid_parameter '-c search_pathh=invalid_name' check_file role '-c role=zz' check_file several_params ' -c role=zz --search_path=public -c statement_timeout=1337 --work-mem=765MB ' check_file search_paths '--search-path="$user",public,popis' check_file search_path_injection '--search-path=public;create/**/table/**/zzz()' check_file spqrguard '-c spqrguard.prevent_distributed_table_modify=on -c spqrguard.prevent_reference_table_modify=off' ody-stop odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/000077500000000000000000000000001526064202200230035ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/invalid_parameter.sql000066400000000000000000000000521526064202200272070ustar00rootroot00000000000000raise exception 'This should not be run'; odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/role.sql000066400000000000000000000000131526064202200244570ustar00rootroot00000000000000show role; odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/search_path_injection.sql000066400000000000000000000000771526064202200300530ustar00rootroot00000000000000show search_path; -- from injection select * from public.zzz; odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/search_paths.sql000066400000000000000000000000221526064202200261620ustar00rootroot00000000000000show search_path; odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/several_params.sql000066400000000000000000000001041526064202200265230ustar00rootroot00000000000000show role; show search_path; show statement_timeout; show work_mem; odyssey-1.6.0-rc1/test/functional/tests/pgoptions/sql/spqrguard.sql000066400000000000000000000001401526064202200255270ustar00rootroot00000000000000show spqrguard.prevent_distributed_table_modify; show spqrguard.prevent_reference_table_modify; odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/000077500000000000000000000000001526064202200230225ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/expected/000077500000000000000000000000001526064202200246235ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/expected/1.out000066400000000000000000000003001526064202200255050ustar00rootroot00000000000000listen example; select pg_sleep(3); pg_sleep ---------- (1 row) Asynchronous notification "example" with payload "Have you seen my coffee cup?" received from server process with PID XXX. odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/odyssey.conf000066400000000000000000000010221526064202200253630ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_pin_on_listen yes } } odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/runner.sh000077500000000000000000000014761526064202200247020ustar00rootroot00000000000000#!/bin/bash set -ex /usr/bin/odyssey /tests/pin_on_listen/odyssey.conf sleep 1 mkdir -p /tests/pin_on_listen/results/ cat /tests/pin_on_listen/sql/1.sql | psql 'host=localhost port=6432 user=postgres dbname=postgres' --echo-all --quiet >/tests/pin_on_listen/results/1.out 2>&1 & listen_pid=$! sleep 1 psql 'host=localhost port=6432 user=postgres dbname=postgres' -c "notify example, 'Have you seen my coffee cup?'" || exit 1 wait $listen_pid || { cat /tests/pin_on_listen/results/1.out exit 1 } # replace PID with XXX, it changes from run to run anyway sed -E -i 's/PID [0-9]+/PID XXX/g' /tests/pin_on_listen/results/1.out diff /tests/pin_on_listen/expected/1.out /tests/pin_on_listen/results/1.out || { cat /tests/pin_on_listen/results/1.out xxd /tests/pin_on_listen/results/1.out exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/sql/000077500000000000000000000000001526064202200236215ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/pin_on_listen/sql/1.sql000066400000000000000000000000451526064202200245010ustar00rootroot00000000000000listen example; select pg_sleep(3); odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/000077500000000000000000000000001526064202200223625ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/.gitignore000066400000000000000000000000141526064202200243450ustar00rootroot00000000000000pstst psmst odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/go.mod000066400000000000000000000001411526064202200234640ustar00rootroot00000000000000module pstmts go 1.23.4 require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 ) odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/go.sum000066400000000000000000000015111526064202200235130ustar00rootroot00000000000000filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/pkg/000077500000000000000000000000001526064202200231435ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/pkg/simple.go000066400000000000000000000025531526064202200247700ustar00rootroot00000000000000package main import ( "context" "log" "sync" "github.com/jmoiron/sqlx" "fmt" "time" _ "github.com/lib/pq" ) var dbname = "db1" func prep() { ctx := context.TODO() var err error db, err := sqlx.ConnectContext(ctx, "postgres", fmt.Sprintf("host=localhost port=6432 user=user1 dbname=%s sslmode=disable", dbname)) if err != nil { log.Fatal(err) } for j := range 10 { var wg sync.WaitGroup wg.Add(10) stmt, err := db.Prepare(fmt.Sprintf("INSERT INTO sh1.foo%d VALUES($1)", j)) if err != nil { log.Fatal(err) } for i := range 10 { go func(wg *sync.WaitGroup) { defer wg.Done() _, err = stmt.Exec(i) if err != nil { log.Fatal(err) } }(&wg) } wg.Add(10) stmt2, err := db.Prepare("SELECT pg_sleep($1)") if err != nil { log.Fatal(err) } for range 10 { go func(wg *sync.WaitGroup) { defer wg.Done() _, err = stmt2.Exec(1) if err != nil { log.Fatal(err) } }(&wg) } wg.Wait() err = stmt.Close() if err != nil { log.Fatal(err) } err = stmt2.Close() if err != nil { log.Fatal(err) } } db.Close() log.Println("OK") } func main() { for range 10 { var wg sync.WaitGroup for range 10 { wg.Add(1) go func() { defer wg.Done() prep() }() } wg.Wait() log.Println("ITER DONE") time.Sleep(2 * time.Second) } log.Println("TEST OK") } odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/pstmts.conf000066400000000000000000000032001526064202200245560ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_file "/var/log/odyssey.log" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers 5 resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 } database "db1" { user "user1" { authentication "none" storage "postgres_server" pool "transaction" pool_size 85 pool_timeout 0 pool_ttl 60 pool_discard no pool_smart_discard no pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 1007 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/prep_stmts/runner.sh000077500000000000000000000003701526064202200242320ustar00rootroot00000000000000#!/usr/bin/env bash set -ex /usr/bin/odyssey /tests/prep_stmts/pstmts.conf sleep 1 /tests/prep_stmts/pstmts-test || { sleep 1 cat /var/log/odyssey.log for i in /asan-output*; do cat $i || true done exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/query_processing/000077500000000000000000000000001526064202200235635ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/query_processing/.gitignore000066400000000000000000000000121526064202200255440ustar00rootroot00000000000000results/* odyssey-1.6.0-rc1/test/functional/tests/query_processing/expected/000077500000000000000000000000001526064202200253645ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/query_processing/expected/virtual_show.out000066400000000000000000000023221526064202200306420ustar00rootroot00000000000000SELECT 1; ?column? ---------- 1 (1 row) SET a.b TO 'z'; SHOW a.b; a.b ----- z (1 row) SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) SET odyssey.target_session_attrs = "read-only"; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ read-only (1 row) SET odyssey.target_session_attrs TO 'read-write'; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ read-write (1 row) SET odyssey.target_session_attrs = "prefer-standby"; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ prefer-standby (1 row) SET odyssey.target_session_attrs TO 'any'; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) odyssey-1.6.0-rc1/test/functional/tests/query_processing/odyssey.conf000066400000000000000000000010471526064202200261330ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp/root2" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_session yes log_stats yes listen { host "127.0.0.1" port 6432 tls "disable" } virtual_processing yes storage "postgres_server" { type "remote" host "127.0.0.1:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" application_name_add_host yes } } odyssey-1.6.0-rc1/test/functional/tests/query_processing/runner.sh000077500000000000000000000007131526064202200254340ustar00rootroot00000000000000#!/bin/bash set -ex # TODO: create more cool tests /usr/bin/odyssey /tests/query_processing/odyssey.conf sleep 1 mkdir /tests/query_processing/results/ cat /tests/query_processing/sql/virtual_show.sql | psql 'host=localhost port=6432 user=postgres dbname=postgres' --echo-all --quiet > /tests/query_processing/results/virtual_show.out 2>&1 diff /tests/query_processing/expected/virtual_show.out /tests/query_processing/results/virtual_show.out ody-stop odyssey-1.6.0-rc1/test/functional/tests/query_processing/sql/000077500000000000000000000000001526064202200243625ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/query_processing/sql/virtual_show.sql000066400000000000000000000006321526064202200276320ustar00rootroot00000000000000 SELECT 1; SET a.b TO 'z'; SHOW a.b; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs = "read-only"; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'read-write'; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs = "prefer-standby"; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'any'; SHOW odyssey.target_session_attrs;odyssey-1.6.0-rc1/test/functional/tests/reload-show-clients/000077500000000000000000000000001526064202200240455ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/reload-show-clients/Makefile000066400000000000000000000002071526064202200255040ustar00rootroot00000000000000test: show_clients reloads show_clients: /tests/reload-show-clients/reloads.sh reloads: /tests/reload-show-clients/show-clients.sh odyssey-1.6.0-rc1/test/functional/tests/reload-show-clients/config.conf000066400000000000000000000037371526064202200261730ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers 5 resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 watchdog { authentication "none" storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM NOW())) - 100" watchdog_lag_interval 10 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres" { user "user1" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes catchup_timeout 10 catchup_checks 10 client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/reload-show-clients/reloads.sh000077500000000000000000000001471526064202200260370ustar00rootroot00000000000000#!/bin/bash set -e sleep 2 for run in {1..1000}; do kill -s HUP $(pidof odyssey) || exit 1 done odyssey-1.6.0-rc1/test/functional/tests/reload-show-clients/runner.sh000077500000000000000000000012441526064202200257160ustar00rootroot00000000000000#!/bin/bash -x /usr/bin/odyssey /tests/reload-show-clients/config.conf make -j 2 -f /tests/reload-show-clients/Makefile || { echo "ERROR: simultaneous reloads with 'show clients' failed" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } sleep 2 # there must be exactly one watchdog coroutine after config reload(s) # function od_storage_watchdog_watch is defined in storage.c if [ $(gdb -q --pid $(pidof odyssey) --batch -ex 'source /gdb.py' -ex 'info mmcoros' | grep -c 'od_storage_watchdog_watch') -ne 1 ] then echo "!!! expected only one lag polling coro after reloads !!!" exit 1 fi ody-stop odyssey-1.6.0-rc1/test/functional/tests/reload-show-clients/show-clients.sh000077500000000000000000000002321526064202200270200ustar00rootroot00000000000000#!/bin/bash set -e sleep 1 for run in {1..1000}; do PGPASSWORD=lolol psql -h localhost -p6432 -dconsole -Uuser1 -c 'show clients' 1>/dev/null done odyssey-1.6.0-rc1/test/functional/tests/reload/000077500000000000000000000000001526064202200214305ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/reload/conf.conf000066400000000000000000000014311526064202200232230ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" log_file "/var/log/odyssey.log" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers 5 resolvers 1 readahead 8192 cache_coroutine 0 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/reload/runner.sh000066400000000000000000000010411526064202200232710ustar00rootroot00000000000000#!/usr/bin/env bash set -ex odyssey /tests/reload/conf.conf sleep 1 psql 'host=localhost port=6432 dbname=postgres user=postgres' -c 'select 1' || { cat /var/log/odyssey.log exit 1 } sed -i 's/host\ "localhost"//g' /tests/reload/conf.conf echo 'unix_socket_dir "/var/run/postgresql"' >> /tests/reload/conf.conf echo 'unix_socket_mode "0777"' >> /tests/reload/conf.conf kill -s HUP $(pidof odyssey) psql 'host=localhost port=6432 dbname=postgres user=postgres' -c 'select 1' || { cat /var/log/odyssey.log exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/rule_address/000077500000000000000000000000001526064202200226365ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/rule_address/addr.conf000066400000000000000000000026051526064202200244220ustar00rootroot00000000000000listen { host "*" port 6432 } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "addr_db" { user "user_addr_correct" "127.0.0.0/24" { authentication "clear_text" password "correct_password" storage "postgres_server" pool "session" } user "user_addr_incorrect" "255.0.0.0/24" { authentication "clear_text" password "correct_password" storage "postgres_server" pool "session" } user "user_addr_default" default { authentication "clear_text" password "correct_password" storage "postgres_server" pool "session" } user "user_addr_empty" { authentication "clear_text" password "correct_password" storage "postgres_server" pool "session" } user "user_addr_hostname_localhost" "localhost" { authentication "clear_text" password "correct_password" storage "postgres_server" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no odyssey-1.6.0-rc1/test/functional/tests/rule_address/runner.sh000077500000000000000000000044021526064202200245060ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/rule_address/addr.conf sleep 1 PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_addr_correct -c "SELECT 1" addr_db > /dev/null 2>&1 || { echo "ERROR: failed auth with correct addr, correct password and plain password in config" cat /var/log/odyssey.log exit 1 } PGPASSWORD=incorrect_password psql -h ip4-localhost -p 6432 -U user_addr_correct -c "SELECT 1" addr_db > /dev/null 2>&1 && { echo "ERROR: successfully auth with correct addr, but incorrect password" cat /var/log/odyssey.log exit 1 } PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_addr_incorrect -c "SELECT 1" addr_db > /dev/null 2>&1 && { echo "ERROR: successfully auth with incorrect addr" cat /var/log/odyssey.log exit 1 } PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_addr_default -c "SELECT 1" addr_db > /dev/null 2>&1 || { echo "ERROR: failed auth with correct addr, correct password and plain password in config" cat /var/log/odyssey.log exit 1 } PGPASSWORD=incorrect_password psql -h ip4-localhost -p 6432 -U user_addr_default -c "SELECT 1" addr_db > /dev/null 2>&1 && { echo "ERROR: successfully auth with correct addr, but incorrect password" cat /var/log/odyssey.log exit 1 } PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_addr_empty -c "SELECT 1" addr_db > /dev/null 2>&1 || { echo "ERROR: failed auth with correct addr, correct password and plain password in config" cat /var/log/odyssey.log exit 1 } PGPASSWORD=incorrect_password psql -h ip4-localhost -p 6432 -U user_addr_empty -c "SELECT 1" addr_db > /dev/null 2>&1 && { echo "ERROR: successfully auth with correct addr, but incorrect password" cat /var/log/odyssey.log exit 1 } PGPASSWORD=correct_password psql -h ip4-localhost -p 6432 -U user_addr_hostname_localhost -c "SELECT 1" addr_db > /dev/null 2>&1 || { echo "ERROR: failed auth with correct addr, correct password and plain password in config" cat /var/log/odyssey.log exit 1 } PGPASSWORD=incorrect_password psql -h ip4-localhost -p 6432 -U user_addr_hostname_localhost -c "SELECT 1" addr_db > /dev/null 2>&1 && { echo "ERROR: successfully auth with correct addr, but incorrect password" cat /var/log/odyssey.log exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/rule_conn_type/000077500000000000000000000000001526064202200232075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/rule_conn_type/odyssey.conf000066400000000000000000000017561526064202200255660ustar00rootroot00000000000000listen { host "*" port 6432 tls "allow" tls_key_file "/tests/rule_conn_type/server.key" tls_cert_file "/tests/rule_conn_type/server.pem" tls_ca_file "/tests/rule_conn_type/root.pem" tls_protocols "tlsv1.2" } storage "pg" { type "remote" host "127.0.0.1" port 5432 } storage "vdb-storage" { type "local" } database "vdb" { user "vuser" { authentication "none" storage "vdb-storage" pool "session" } } database "db1" { user "user1" host { authentication "none" storage "pg" pool "session" } } database "tsa_db" { user "user_ro" hostssl { authentication "none" storage "pg" pool "session" } user "user_rw" hostnossl { authentication "none" storage "pg" pool "session" } } daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug yes log_session yes log_stats no log_query no odyssey-1.6.0-rc1/test/functional/tests/rule_conn_type/runner.sh000077500000000000000000000036571526064202200250720ustar00rootroot00000000000000#!/bin/bash -x set -ex pushd /tests/rule_conn_type/ rm -f root* rm -f server* openssl genrsa -out root.key 2048 openssl req -new -key root.key -out root.csr -nodes -subj "/CN=odyssey-test-cn" openssl x509 -req -days 2 -in root.csr -signkey root.key -out root.pem openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -nodes -subj "/CN=localhost" openssl x509 -req -in server.csr -CA root.pem -CAkey root.key -CAcreateserial -out server.pem -days 2 popd /usr/bin/odyssey /tests/rule_conn_type/odyssey.conf sleep 1 # db1.user1 has host connection type - should work with ssl and without psql 'host=localhost port=6432 user=user1 dbname=db1 sslmode=disable' -c 'select 42' || { echo 'db1.user1 should work without ssl' sleep 1 cat /var/log/odyssey.log exit 1 } psql 'host=localhost port=6432 user=user1 dbname=db1 sslmode=verify-full sslrootcert=/tests/rule_conn_type/root.pem' -c 'select 42' || { echo 'db1.user1 should work with ssl' sleep 1 cat /var/log/odyssey.log exit 1 } # tsa_db.user_ro has hostsll connection type - only connections with ssl should work psql 'host=localhost port=6432 user=user_ro dbname=tsa_db sslmode=verify-full sslrootcert=/tests/rule_conn_type/root.pem' -c 'select 42' || { echo 'tsa_db.user_ro should work with ssl' sleep 1 cat /var/log/odyssey.log exit 1 } psql 'host=localhost port=6432 user=user_ro dbname=tsa_db sslmode=disable' -c 'select 42' && { echo 'tsa_db.user_ro should route only with ssl enabled' exit 1 } # tsa_db.user_rw has hostnosll connection type - only connections without ssl should work psql 'host=localhost port=6432 user=user_rw dbname=tsa_db sslmode=verify-full sslrootcert=/tests/rule_conn_type/root.pem' -c 'select 42' && { echo 'tsa_db.user_rw should not work with ssl' exit 1 } psql 'host=localhost port=6432 user=user_rw dbname=tsa_db sslmode=disable' -c 'select 42' || { echo 'tsa_db.user_rw should route only with ssl disabled' exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/rules_order/000077500000000000000000000000001526064202200225075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/rules_order/not_seq_1.conf000066400000000000000000000034341526064202200252520ustar00rootroot00000000000000listen { host "*" port 6432 } storage "pg" { type "remote" host "127.0.0.1" port 5432 } storage "vdb-storage" { type "local" } database "vdb" { user "vuser" { authentication "none" storage "vdb-storage" pool "session" } } database "db1" { user "db1u1" host { authentication "none" storage "pg" pool "transaction" } user "db1u1" { authentication "none" storage "pg" pool "transaction" } user "db1u2" { authentication "none" storage "pg" pool "transaction" } user "db1u1" "10.0.14.89/24" { authentication "none" storage "pg" pool "transaction" } user "db1u2" "10.13.37.10/24" { authentication "none" storage "pg" pool "transaction" } user "db1u2" hostssl "10.13.37.10/24" { authentication "none" storage "pg" pool "transaction" } user default "10.14.89.10/24" { authentication "none" storage "pg" pool "transaction" } user default "10.14.89.10/24" local { authentication "none" storage "pg" pool "transaction" } } database default { user "user1" { authentication "none" storage "pg" pool "transaction" } user "user1" "10.0.0.12/24" { authentication "none" storage "pg" pool "transaction" } user default hostnossl { authentication "none" storage "pg" pool "transaction" } user default "localhost" { authentication "none" storage "pg" pool "transaction" } user default { authentication "none" storage "pg" pool "transaction" } } daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout yes log_config yes log_debug no log_session yes log_stats no log_query no odyssey-1.6.0-rc1/test/functional/tests/rules_order/not_seq_1.out000066400000000000000000000020071526064202200251270ustar00rootroot00000000000000show rules; database | user | address | connection_type | obsolete -----------+-----------+----------------+-----------------+---------- db1 | db1u2 | 10.13.37.10/24 | hostssl | f db1 | db1u1 | 10.0.14.89/24 | all | f db1 | db1u2 | 10.13.37.10/24 | all | f db1 | db1u1 | | host | f db1 | db1u1 | | all | f db1 | db1u2 | | all | f vdb | vuser | | all | f db1 | | 10.14.89.10/24 | local | f db1 | | 10.14.89.10/24 | all | f | user1 | 10.0.0.12/24 | all | f | user1 | | all | f | | localhost | all | f | | | hostnossl | f | | | all | f (14 rows) odyssey-1.6.0-rc1/test/functional/tests/rules_order/not_seq_1_reload.conf000066400000000000000000000026561526064202200266050ustar00rootroot00000000000000listen { host "*" port 6432 } storage "pg" { type "remote" host "127.0.0.1" port 5432 } storage "vdb-storage" { type "local" } database "vdb" { user "vuser" { authentication "none" storage "vdb-storage" pool "session" } } database "db1" { user "db1u2" host "10.13.37.10/24" { authentication "none" storage "pg" pool "transaction" } user default "10.14.89.10/24" local { authentication "none" storage "pg" pool "transaction" } user "db1u1reload" hostnossl { authentication "none" storage "pg" pool "transaction" } user "db1u2" { authentication "none" storage "pg" pool "transaction" } user "db1u1reload" "10.0.14.89/24" { authentication "none" storage "pg" pool "transaction" } } database default { user default "localhost" { authentication "none" storage "pg" pool "transaction" } user "user1" "10.0.0.12/24" hostnossl { authentication "none" storage "pg" pool "transaction" } user "user1reload" { authentication "none" storage "pg" pool "transaction" } user default { authentication "none" storage "pg" pool "transaction" } } daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout yes log_config yes log_debug no log_session yes log_stats no log_query no odyssey-1.6.0-rc1/test/functional/tests/rules_order/not_seq_1_reload.out000066400000000000000000000014471526064202200264640ustar00rootroot00000000000000show rules; database | user | address | connection_type | obsolete -----------+-------------+----------------+-----------------+---------- db1 | db1u2 | 10.13.37.10/24 | host | f db1 | db1u1reload | 10.0.14.89/24 | all | f db1 | db1u1reload | | hostnossl | f db1 | db1u2 | | all | f vdb | vuser | | all | f db1 | | 10.14.89.10/24 | local | f | user1 | 10.0.0.12/24 | hostnossl | f | user1reload | | all | f | | localhost | all | f | | | all | f (10 rows) odyssey-1.6.0-rc1/test/functional/tests/rules_order/runner.sh000077500000000000000000000061011526064202200243550ustar00rootroot00000000000000#!/bin/bash -x set -ex mkdir /tests/rules_order/results/ /usr/bin/odyssey /tests/rules_order/seq_1.conf sleep 1 timeout 1s psql 'host=localhost port=6432 user=vuser dbname=vdb' -c 'show rules;' --echo-all --quiet > /tests/rules_order/results/seq_1.out 2>&1 || { cat /tests/rules_order/results/seq_1.out exit 1 } diff /tests/rules_order/seq_1.out /tests/rules_order/results/seq_1.out || { echo "start /tests/rules_order/seq_1.out" cat /tests/rules_order/seq_1.out echo "end /tests/rules_order/seq_1.out" echo echo "start /tests/rules_order/results/seq_1.out" cat /tests/rules_order/results/seq_1.out echo "end /tests/rules_order/results/seq_1.out" exit 1 } rm /tests/rules_order/seq_1.conf cp /tests/rules_order/seq_1_reload.conf /tests/rules_order/seq_1.conf kill -sHUP $(pidof odyssey) sleep 2 timeout 1s psql 'host=localhost port=6432 user=vuser dbname=vdb' -c 'show rules;' --echo-all --quiet > /tests/rules_order/results/seq_1_reload.out 2>&1 || { cat /tests/rules_order/results/seq_1_reload.out exit 1 } diff /tests/rules_order/seq_1_reload.out /tests/rules_order/results/seq_1_reload.out || { echo "start /tests/rules_order/seq_1_reload.out" cat /tests/rules_order/seq_1_reload.out echo "end /tests/rules_order/seq_1_reload.out" echo echo "start /tests/rules_order/results/seq_1_reload.out" cat /tests/rules_order/results/seq_1_reload.out echo "end /tests/rules_order/results/seq_1_reload.out" cat /var/log/odyssey.log exit 1 } ody-stop echo "" > log_file "/var/log/odyssey.log" /usr/bin/odyssey /tests/rules_order/not_seq_1.conf sleep 1 timeout 1s psql 'host=localhost port=6432 user=vuser dbname=vdb' -c 'show rules;' --echo-all --quiet > /tests/rules_order/results/not_seq_1.out 2>&1 || { cat /tests/rules_order/results/not_seq_1.out exit 1 } diff /tests/rules_order/not_seq_1.out /tests/rules_order/results/not_seq_1.out || { echo "start /tests/rules_order/not_seq_1.out" cat /tests/rules_order/not_seq_1.out echo "end /tests/rules_order/not_seq_1.out" echo echo "start /tests/rules_order/results/not_seq_1.out" cat /tests/rules_order/results/not_seq_1.out echo "end /tests/rules_order/results/not_seq_1.out" exit 1 } rm /tests/rules_order/not_seq_1.conf cp /tests/rules_order/not_seq_1_reload.conf /tests/rules_order/not_seq_1.conf kill -sHUP $(pidof odyssey) sleep 2 timeout 1s psql 'host=localhost port=6432 user=vuser dbname=vdb' -c 'show rules;' --echo-all --quiet > /tests/rules_order/results/not_seq_1_reload.out 2>&1 || { cat /tests/rules_order/results/not_seq_1_reload.out exit 1 } diff /tests/rules_order/not_seq_1_reload.out /tests/rules_order/results/not_seq_1_reload.out || { echo "start /tests/rules_order/not_seq_1_reload.out" cat /tests/rules_order/not_seq_1_reload.out echo "end /tests/rules_order/not_seq_1_reload.out" echo echo "start /tests/rules_order/results/not_seq_1_reload.out" cat /tests/rules_order/results/not_seq_1_reload.out echo "end /tests/rules_order/results/not_seq_1_reload.out" exit 1 } ody-stopodyssey-1.6.0-rc1/test/functional/tests/rules_order/seq_1.conf000066400000000000000000000034641526064202200243750ustar00rootroot00000000000000listen { host "*" port 6432 } storage "pg" { type "remote" host "127.0.0.1" port 5432 } storage "vdb-storage" { type "local" } database "vdb" { user "vuser" { authentication "none" storage "vdb-storage" pool "session" } } database "db1" { user "db1u1" host { authentication "none" storage "pg" pool "transaction" } user "db1u1" { authentication "none" storage "pg" pool "transaction" } user "db1u2" { authentication "none" storage "pg" pool "transaction" } user "db1u1" "10.0.14.89/24" { authentication "none" storage "pg" pool "transaction" } user "db1u2" "10.13.37.10/24" { authentication "none" storage "pg" pool "transaction" } user "db1u2" hostssl "10.13.37.10/24" { authentication "none" storage "pg" pool "transaction" } user default "10.14.89.10/24" { authentication "none" storage "pg" pool "transaction" } user default "10.14.89.10/24" local { authentication "none" storage "pg" pool "transaction" } } database default { user "user1" { authentication "none" storage "pg" pool "transaction" } user "user1" "10.0.0.12/24" { authentication "none" storage "pg" pool "transaction" } user default hostnossl { authentication "none" storage "pg" pool "transaction" } user default "localhost" { authentication "none" storage "pg" pool "transaction" } user default { authentication "none" storage "pg" pool "transaction" } } daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout yes log_config yes log_debug no log_session yes log_stats no log_query no sequential_routing yes odyssey-1.6.0-rc1/test/functional/tests/rules_order/seq_1.out000066400000000000000000000020071526064202200242470ustar00rootroot00000000000000show rules; database | user | address | connection_type | obsolete -----------+-----------+----------------+-----------------+---------- vdb | vuser | | all | f db1 | db1u1 | | host | f db1 | db1u1 | | all | f db1 | db1u2 | | all | f db1 | db1u1 | 10.0.14.89/24 | all | f db1 | db1u2 | 10.13.37.10/24 | all | f db1 | db1u2 | 10.13.37.10/24 | hostssl | f db1 | | 10.14.89.10/24 | all | f db1 | | 10.14.89.10/24 | local | f | user1 | | all | f | user1 | 10.0.0.12/24 | all | f | | | hostnossl | f | | localhost | all | f | | | all | f (14 rows) odyssey-1.6.0-rc1/test/functional/tests/rules_order/seq_1_reload.conf000066400000000000000000000027061526064202200257210ustar00rootroot00000000000000listen { host "*" port 6432 } storage "pg" { type "remote" host "127.0.0.1" port 5432 } storage "vdb-storage" { type "local" } database "vdb" { user "vuser" { authentication "none" storage "vdb-storage" pool "session" } } database "db1" { user "db1u2" host "10.13.37.10/24" { authentication "none" storage "pg" pool "transaction" } user default "10.14.89.10/24" local { authentication "none" storage "pg" pool "transaction" } user "db1u1reload" hostnossl { authentication "none" storage "pg" pool "transaction" } user "db1u2" { authentication "none" storage "pg" pool "transaction" } user "db1u1reload" "10.0.14.89/24" { authentication "none" storage "pg" pool "transaction" } } database default { user default "localhost" { authentication "none" storage "pg" pool "transaction" } user "user1" "10.0.0.12/24" hostnossl { authentication "none" storage "pg" pool "transaction" } user "user1reload" { authentication "none" storage "pg" pool "transaction" } user default { authentication "none" storage "pg" pool "transaction" } } daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout yes log_config yes log_debug no log_session yes log_stats no log_query no sequential_routing yes odyssey-1.6.0-rc1/test/functional/tests/rules_order/seq_1_reload.out000066400000000000000000000014471526064202200256040ustar00rootroot00000000000000show rules; database | user | address | connection_type | obsolete -----------+-------------+----------------+-----------------+---------- vdb | vuser | | all | f db1 | db1u2 | 10.13.37.10/24 | host | f db1 | | 10.14.89.10/24 | local | f db1 | db1u1reload | | hostnossl | f db1 | db1u2 | | all | f db1 | db1u1reload | 10.0.14.89/24 | all | f | | localhost | all | f | user1 | 10.0.0.12/24 | hostnossl | f | user1reload | | all | f | | | all | f (10 rows) odyssey-1.6.0-rc1/test/functional/tests/scram/000077500000000000000000000000001526064202200212675ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/scram/config.conf000066400000000000000000000034521526064202200234070ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "scram_db" { user "backend_auth_with_correct_password" { authentication "none" storage "postgres_server" storage_db "scram_db" storage_user "scram_user" storage_password "scram_user_password" pool "session" } user "backend_auth_with_incorrect_password" { authentication "none" storage "postgres_server" storage_db "scram_db" storage_user "scram_user" storage_password "not_scram_user_password" pool "session" } user "frontend_auth_plain" { authentication "scram-sha-256" password "correct_password" storage "postgres_server" storage_db "scram_db" storage_user "scram_user" storage_password "scram_user_password" pool "session" } user "frontend_auth_scram_secret" { authentication "scram-sha-256" password "SCRAM-SHA-256$4096:THzbzuckxnvrH94wNptooQ==$LNODWWqZwoRU9UlkRlh1XzV+mi4BT2VkaAlikOLkdhw=:4d5cNgUHYb2vpHIHohG7j/Ww6I1u/KQgxPhA+68jZOI=" storage "postgres_server" storage_db "scram_db" storage_user "scram_user" storage_password "scram_user_password" pool "session" } user "frontend_and_backend_auth_scram_password" { authentication "scram-sha-256" password "scram_user_password" storage "postgres_server" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 # do not exists, just to make sure we do not offer scram-sha256-plus tls "allow" tls_key_file "/tls-compat/server.key" tls_cert_file "/tls-compat/server.pem" tls_ca_file "/tls-compat/root.pem" tls_protocols "tlsv1.2" } odyssey-1.6.0-rc1/test/functional/tests/scram/runner.sh000077500000000000000000000003241526064202200231360ustar00rootroot00000000000000#!/bin/bash -x /usr/bin/odyssey /tests/scram/config.conf sleep 3 /tests/scram/test_scram_backend.sh if [ $? -eq 1 ] then exit 1 fi /tests/scram/test_scram_frontend.sh if [ $? -eq 1 ] then exit 1 fi ody-stop odyssey-1.6.0-rc1/test/functional/tests/scram/test_scram_backend.sh000077500000000000000000000106151526064202200254440ustar00rootroot00000000000000#!/bin/bash -x for _ in $(seq 1 5); do psql 'host=ip4-localhost port=6432 user=backend_auth_with_incorrect_password dbname=scram_db sslmode=disable' -c "SELECT 1" 2>&1 && { echo "ERROR: successfully backend auth with incorrect password" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } done for _ in $(seq 1 5); do psql 'host=ip4-localhost port=6432 user=backend_auth_with_correct_password dbname=scram_db sslmode=disable' -c "SELECT 1" 2>&1 || { echo "ERROR: failed backend auth with correct password" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } done for _ in $(seq 1 5); do PGPASSWORD=scram_user_password psql 'host=ip4-localhost port=6432 user=frontend_and_backend_auth_scram_password dbname=scram_db sslmode=disable' -c "SELECT 1" 2>&1 || { echo "ERROR: failed frontend+backend auth with scram password" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } done for _ in $(seq 1 5); do PGPASSWORD=scram_user_password_incorrect psql 'host=ip4-localhost port=6432 user=frontend_and_backend_auth_scram_password dbname=scram_db sslmode=disable' -c "SELECT 1" 2>&1 && { echo "ERROR: successfully backend auth with incorrect password" sleep 1 cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } done original_verifier=$(psql -h ip4-localhost -U postgres -d scram_db -At \ -v ON_ERROR_STOP=1 -c "SELECT rolpassword FROM pg_authid WHERE rolname = 'scram_user'") || exit 1 restore_password() { psql -h ip4-localhost -U postgres -d scram_db -v ON_ERROR_STOP=1 \ -v verifier="$original_verifier" <<'SQL' || exit 1 ALTER ROLE scram_user PASSWORD :'verifier'; SQL } trap restore_password EXIT for salt_len in 8 32; do echo "Testing backend SCRAM with salt length $salt_len" verifier=$(python3 - "$salt_len" <<'PY' import base64 import hashlib import hmac import sys salt = bytes(range(int(sys.argv[1]))) salted_password = hashlib.pbkdf2_hmac('sha256', b'scram_user_password', salt, 4096) client_key = hmac.digest(salted_password, b'Client Key', 'sha256') stored_key = hashlib.sha256(client_key).digest() server_key = hmac.digest(salted_password, b'Server Key', 'sha256') salt, stored_key, server_key = ( base64.b64encode(value).decode() for value in (salt, stored_key, server_key) ) print(f'SCRAM-SHA-256$4096:{salt}${stored_key}:{server_key}') PY ) || exit 1 psql -h ip4-localhost -U postgres -d scram_db -v ON_ERROR_STOP=1 \ -v verifier="$verifier" <<'SQL' || exit 1 ALTER ROLE scram_user PASSWORD :'verifier'; SQL # Drop pooled connections so the new verifier is used for authentication. ody-stop || exit 1 /usr/bin/odyssey /tests/scram/config.conf || exit 1 for _ in $(seq 1 50); do pg_isready -q -h ip4-localhost -p 6432 -d scram_db \ -U backend_auth_with_correct_password && break sleep 0.1 done PGPASSWORD=scram_user_password psql -w -v ON_ERROR_STOP=1 \ 'host=ip4-localhost port=5432 user=scram_user dbname=scram_db sslmode=disable' -c "SELECT 1" || exit 1 for _ in $(seq 1 5); do psql -w -v ON_ERROR_STOP=1 \ 'host=ip4-localhost port=6432 user=backend_auth_with_correct_password dbname=scram_db sslmode=disable' -c "SELECT 1" 2>&1 || { echo "ERROR: failed backend auth with salt length $salt_len" cat /var/log/odyssey.log exit 1 } done psql -w -v ON_ERROR_STOP=1 \ 'host=ip4-localhost port=6432 user=backend_auth_with_incorrect_password dbname=scram_db sslmode=disable' -c "SELECT 1" 2>&1 && { echo "ERROR: accepted incorrect password with salt length $salt_len" cat /var/log/odyssey.log exit 1 } done exit 0 odyssey-1.6.0-rc1/test/functional/tests/scram/test_scram_frontend.sh000077500000000000000000000025151526064202200256740ustar00rootroot00000000000000#!/bin/bash -x for _ in $(seq 1 5); do psql 'host=localhost port=6432 user=frontend_auth_plain dbname=scram_db password=incorrect_password sslmode=disable' -c "SELECT 1" 2>&1 && { echo "ERROR: successfully auth with incorrect password and plain password in config" ody-stop exit 1 } done for _ in $(seq 1 5); do psql 'host=localhost port=6432 user=frontend_auth_plain dbname=scram_db password=correct_password sslmode=disable' -c "SELECT 1" 2>&1 || { echo "ERROR: failed auth with correct password and plain password in config" ody-stop exit 1 } done for _ in $(seq 1 5); do psql 'host=localhost port=6432 user=frontend_auth_scram_secret dbname=scram_db password=incorrect_password sslmode=disable' -c "SELECT 1" 2>&1 && { echo "ERROR: successfully auth with incorrect password and scram secret in config" ody-stop exit 1 } done for _ in $(seq 1 5); do psql 'host=localhost port=6432 user=frontend_auth_scram_secret dbname=scram_db password=correct_password sslmode=disable' -c "SELECT 1" 2>&1 || { echo "ERROR: failed auth with correct password and scram secret in config" ody-stop exit 1 } done odyssey-1.6.0-rc1/test/functional/tests/server-lifetime/000077500000000000000000000000001526064202200232645ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/server-lifetime/pool_ttl.conf000066400000000000000000000012211526064202200257630ustar00rootroot00000000000000storage "postgres_server" { host "[127.0.0.1]:5432" type "remote" } storage "console" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" pool_ttl 5 } } database "console" { user "console" { authentication "none" storage "console" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/server-lifetime/runner.sh000077500000000000000000000031101526064202200251270ustar00rootroot00000000000000#!/bin/bash -x set -ex check_servers_expiration() { pgbench 'host=localhost port=6432 user=postgres dbname=postgres' -T 2 -j 1 -c 5 --no-vacuum --progress 1 --select-only date -R # do not forget remove empty lines by sed psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' servers_count=`psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | wc -l` if [ $servers_count != 5 ]; then echo 'there must be 5 servers' cat /var/log/odyssey.log exit 1 fi sleep 2 date -R # lifetime or pool_ttl not expired psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' servers_count=`psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | wc -l` if [ $servers_count != 5 ]; then echo 'there must be 5 servers' cat /var/log/odyssey.log exit 1 fi # wait for lifetime or pool_ttl to expire # get some higher value than total lifetime, because closing connections # gets some time sleep 6 date -R psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' servers_count=`psql 'host=localhost port=6432 user=console dbname=console' -t -c 'show servers' | sed '/^$/d' | wc -l` if [ $servers_count != 0 ]; then echo 'there must be 0 servers, because of its lifetime' cat /var/log/odyssey.log exit 1 fi } /usr/bin/odyssey /tests/server-lifetime/pool_ttl.conf sleep 1 check_servers_expiration || exit 1 ody-stop /usr/bin/odyssey /tests/server-lifetime/server_lifetime.conf sleep 1 check_servers_expiration || exit 1 ody-stop odyssey-1.6.0-rc1/test/functional/tests/server-lifetime/server_lifetime.conf000066400000000000000000000012301526064202200273130ustar00rootroot00000000000000storage "postgres_server" { host "[127.0.0.1]:5432" type "remote" } storage "console" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" server_lifetime 5 } } database "console" { user "console" { authentication "none" storage "console" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/shared_pool/000077500000000000000000000000001526064202200224615ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/shared_pool/odyssey.conf000066400000000000000000000014711526064202200250320ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } storage "postgres_server" { host "[127.0.0.1]:5432" type "remote" } storage "console" { type "local" } shared_pool "idm" { pool_size 2 } database "db1" { user "user1" { authentication "none" storage "postgres_server" shared_pool "idm" pool "session" } } database "postgres" { user "user1" { authentication "none" storage "postgres_server" shared_pool "idm" pool "session" } } database "console" { user "console" { authentication "none" storage "console" pool "session" } } odyssey-1.6.0-rc1/test/functional/tests/shared_pool/runner.sh000077500000000000000000000016621526064202200243360ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/shared_pool/odyssey.conf sleep 1 psql 'host=localhost port=6432 user=user1 dbname=db1' -c 'select pg_sleep(5)' & first_pid=$! sleep 0.5 psql 'host=localhost port=6432 user=user1 dbname=postgres' -c 'select pg_sleep(5)' & second_pid=$! sleep 0.5 psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' set +e timeout 1s psql 'host=localhost port=6432 user=user1 dbname=db1' -c 'select 42' ret=$? set -e if [ $ret -ne 124 ]; then echo "Expected timeout (124), got $ret" cat /var/log/odyssey.log exit 1 fi set +e timeout 1s psql 'host=localhost port=6432 user=user1 dbname=postgres' -c 'select 42' ret=$? set -e if [ $ret -ne 124 ]; then echo "Expected timeout (124), got $ret" cat /var/log/odyssey.log exit 1 fi wait $first_pid || { cat /var/log/odyssey.log exit 1 } wait $second || { cat /var/log/odyssey.log exit 1 } ody-stop odyssey-1.6.0-rc1/test/functional/tests/shell-test/000077500000000000000000000000001526064202200222465ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/shell-test/conf.conf000066400000000000000000000056431526064202200240520ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize no unix_socket_dir "/tmp" unix_socket_mode "0644" pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers 5 resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 watchdog { authentication "none" # makes no sense storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" # 1 for cron & 9 for console db queries pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM NOW())) - 100" # watchdog_lag_query "SELECT 1" watchdog_lag_interval 10 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres" { user "user1" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes catchup_timeout 10 catchup_checks 10 client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } user "useropt" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes storage_user "postgres" client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 options { "statement_timeout" "0" } } } storage "local" { type "local" } database "console" { user "stat" { authentication "none" role "stat" pool "session" storage "local" } user "admin" { authentication "none" role "admin" pool "session" storage "local" } user "admin2" { authentication "none" role "stat" pool "session" storage "local" } user "rogue" { authentication "none" role "notallow" pool "session" storage "local" } user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/shell-test/conf2.conf000066400000000000000000000055051526064202200241310ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session yes log_query no log_stats yes stats_interval 60 workers 5 resolvers 1 readahead 8192 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 watchdog { authentication "none" # makes no sense storage "postgres_server" storage_db "postgres" storage_user "postgres" pool_routing "internal" pool "transaction" # 1 for cron & 9 for console db queries pool_size 10 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes server_lifetime 1901 log_debug no watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM NOW())) - 100" # watchdog_lag_query "SELECT 1" watchdog_lag_interval 10 } } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } } database "postgres" { user "user1" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes catchup_timeout 10 catchup_checks 10 client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 } user "useropt" { authentication "none" storage "postgres_server" pool "transaction" pool_size 0 pool_timeout 0 pool_ttl 60 pool_discard no pool_cancel yes pool_rollback yes storage_user "postgres" client_fwd_error yes application_name_add_host yes server_lifetime 3600 log_debug no quantiles "0.99,0.95,0.5" client_max 107 options { "statement_timeout" "0" } } } storage "local" { type "local" } database "console" { user "stat" { authentication "none" role "stat" pool "session" storage "local" } user "admin" { authentication "none" role "stat" pool "session" storage "local" } user "admin2" { authentication "none" role "admin" pool "session" storage "local" } user "rogue" { authentication "none" role "notallow" pool "session" storage "local" } user default { authentication "none" role "admin" pool "session" storage "local" } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart yes bindwith_reuseport yes odyssey-1.6.0-rc1/test/functional/tests/shell-test/console_role_test.sh000077500000000000000000000033251526064202200263320ustar00rootroot00000000000000#!/bin/bash -x # Tests are based on fact that in case of insufficient privilege # psql return nothing and all of logging done by odyssey sleep 1 /usr/bin/odyssey /tests/shell-test/conf.conf response=$(psql -U rogue -d console -h localhost -p 6432 -c 'show errors;') if [[ $response != "" ]]; then exit 1 fi response=$(psql -U stat -d console -h localhost -p 6432 -c 'show errors;') if [[ $response == "" ]]; then exit 1 fi response=$(psql -U stat -d console -h localhost -p 6432 -c 'reload;') if [[ $response != "" ]]; then exit 1 fi response=$(psql -U admin -d console -h localhost -p 6432 -c 'reload;') if [[ $response == "" ]]; then exit 1 fi #Change of roles online mv /tests/shell-test/conf.conf /tests/shell-test/tmp mv /tests/shell-test/conf2.conf /tests/shell-test/conf.conf mv /tests/shell-test/tmp /tests/shell-test/conf2.conf #First call actually reload config, second check what privilege of admin are dropped response=$(psql -U admin -d console -h localhost -p 6432 -c 'reload;') response=$(psql -U admin -d console -h localhost -p 6432 -c 'reload;') if [[ $response != "" ]]; then mv /tests/shell-test/conf2.conf /tests/shell-test/tmp mv /tests/shell-test/conf.conf /tests/shell-test/conf2.conf mv /tests/shell-test/tmp /tests/shell-test/conf.conf exit 1 fi response=$(psql -U admin2 -d console -h localhost -p 6432 -c 'reload;') if [[ $response == "" ]]; then mv /tests/shell-test/conf2.conf /tests/shell-test/tmp mv /tests/shell-test/conf.conf /tests/shell-test/conf2.conf mv /tests/shell-test/tmp /tests/shell-test/conf.conf exit 1 fi mv /tests/shell-test/conf2.conf /tests/shell-test/tmp mv /tests/shell-test/conf.conf /tests/shell-test/conf2.conf mv /tests/shell-test/tmp /tests/shell-test/conf.conf odyssey-1.6.0-rc1/test/functional/tests/shell-test/log_error_rate_test.sh000077500000000000000000000005601526064202200266520ustar00rootroot00000000000000#!/bin/bash -x #kill -9 $(ps aux | grep odyssey | grpe -v grep | awk '{print $2}') sleep 1 #ody-start #./odyssey ./ody.conf for _ in $(seq 1 4000000); do for __ in $(seq 1 3000); do # psql -U user1 -d postgres -h localhost -p 6432 -c 'SELECT 1/0' & psql -U user1 -d postgres -h localhost -p 6432 -c 'select pg_sleep(0.01)' & done done odyssey-1.6.0-rc1/test/functional/tests/shell-test/override_pg_options_test.sh000077500000000000000000000022511526064202200277240ustar00rootroot00000000000000 set -ex PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show search_path' | grep tpath PGOPTIONS="-c role=zz" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show role' | grep zz PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show statement_timeout' | grep "0" # override PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show lock_timeout' | grep "10s" PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show search_path' | grep tpath PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show statement_timeout' | grep "0" #override PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=useropt" -c 'show lock_timeout' | grep "10s" odyssey-1.6.0-rc1/test/functional/tests/shell-test/parse_pg_options_test.sh000077500000000000000000000140211526064202200272150ustar00rootroot00000000000000 set -ex PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS=" -c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS=" --search_path=tpath t=5min 10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS=" --search_path=tpath --lock_timeout=10s " psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS="--search_path=tpath --statement_timeout=5min --lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock _timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock _timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock _timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep -v "10s" PGOPTIONS=" -c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS=" -c search_path=tpath -c statement_timeout=5min -c lock_timeout=10s " psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show lock_timeout' | grep "10s" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout= " psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show search_path' | grep tpath PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c " psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'show statement_timeout' | grep "5min" PGOPTIONS="-c search_path=tpath -c statement_timeout=5min -c lock_timeout=invalid" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'select 1' PGOPTIONS="-c search_path=tpath -c statement_timeout=invalid -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'select 1' PGOPTIONS="-c search_path=invalid -c statement_timeout=5min -c lock_timeout=10s" psql "host=localhost port=6432 dbname=postgres user=postgres" -c 'select 1' # tests for many search_path parametrs PGOPTIONS=--search_path=pgstac,public psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *pgstac, *public *$" PGOPTIONS="--search_path=pgstac,public" psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *pgstac, *public *$" PGOPTIONS=--search_path="pgstac,public" psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *pgstac, *public *$" PGOPTIONS='-c search_path=pgstac,public' psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *pgstac, *public *$" PGOPTIONS='-c search_path=pgstac,public,test' psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *pgstac, *public, *test *$" PGOPTIONS='-c search_path="pgstac",public' psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *\"pgstac\", *public *$" PGOPTIONS='-c search_path=pgstac,"public"' psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *pgstac, *\"public\" *$" PGOPTIONS='-c search_path=pgstac -c search_path=public' psql "host=localhost port=6432 dbname=postgres user=postgres" -t -c 'show search_path' | grep -E "^ *public *$"odyssey-1.6.0-rc1/test/functional/tests/shell-test/pool_size.conf000066400000000000000000000012301526064202200251140ustar00rootroot00000000000000pid_file "/var/run/odyssey.pid" daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session yes log_query no log_stats yes listen { host "*" port 6432 backlog 128 } storage "postgres_server" { type "remote" host "localhost" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 10 pool_timeout 3000 # We expect 3 clients to do pg_sleep(1) each. Extra second to suppress falpping. } } storage "local" { type "local" } locks_dir "/tmp/odyssey" odyssey-1.6.0-rc1/test/functional/tests/shell-test/pool_size_test.sh000077500000000000000000000007161526064202200256530ustar00rootroot00000000000000#!/bin/bash -x set -e ody-stop # We set pool size to 1 and check that 3 clients can do pg_sleep(1) at once. # We expect them to wait serially on 1 backend. /usr/bin/odyssey /tests/shell-test/pool_size.conf sleep 1 for _ in $(seq 1 300); do psql -h 0.0.0.0 -p 6432 -c 'select pg_sleep(0.1)' -U user1 -d postgres & done for _ in $(seq 1 300); do wait -n || { code="$?" ([[ $code = "127" ]] && exit 0 || exit "$code") break } done; ody-stopodyssey-1.6.0-rc1/test/functional/tests/shell-test/runner.sh000077500000000000000000000020301526064202200241110ustar00rootroot00000000000000#!/bin/bash -ex # TODO: rewrite #/shell-test/test.sh /tests/shell-test/console_role_test.sh /tests/shell-test/parse_pg_options_test.sh /tests/shell-test/override_pg_options_test.sh /tests/shell-test/pool_size_test.sh ody-stop #kill -9 $(ps aux | grep odyssey | grpe -v grep | awk '{print $2}') # sleep 1 #ody-start # ./build/sources/odyssey ./odyssey-dev.conf # for _ in $(seq 1 40); do # sleep 0.1 # for __ in $(seq 1 10); do # psql -U postgres -d postgres -h 0.0.0.0 -p 6432 -c 'select pg_sleep(39)' & # psql -U postgres -d postgres -h localhost -p 6432 -c 'select 1' & # psql -U postgres -d postgres -h 0.0.0.0 -p 6432 -c 'select pg_sleep(1)' & # done # #ody-restart # ps uax | grep odys # ./build/sources/odyssey ./odyssey-dev.conf # for __ in $(seq 1 30); do # psql -U postgres -d postgres -h localhost -p 6432 -c 'select 1' & # psql -U postgres -d postgres -h 0.0.0.0 -p 6432 -c 'select pg_sleep(39)' & # psql -U postgres -d postgres -h 0.0.0.0 -p 6432 -c 'select pg_sleep(1)' & # done # done odyssey-1.6.0-rc1/test/functional/tests/shell-test/test_sleep.sh000077500000000000000000000004261526064202200247560ustar00rootroot00000000000000#!/bin/bash -x #kill -9 $(ps aux | grep odyssey | grpe -v grep | awk '{print $2}') sleep 1 #ody-start for _ in $(eq 1 100); do psql -h localhost -p 6432 -c 'select 1' -U user1 -d postgres & psql -h 0.0.0.0 -p 6432 -c 'select pg_sleep(100)' -U user1 -d postgres & done odyssey-1.6.0-rc1/test/functional/tests/show-config/000077500000000000000000000000001526064202200224055ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/show-config/conf.conf000066400000000000000000000012471526064202200242050ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no workers 2 resolvers 1 listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/show-config/runner.sh000077500000000000000000000050331526064202200242560ustar00rootroot00000000000000#!/bin/bash # SHOW CONFIG reports the configuration of the running process, not the # file on disk. RELOAD applies only part of the global configuration, so # editing odyssey.conf and reloading is not enough to tell what is in # effect - that is what this test checks. set -uex CONF=/tmp/show-config.conf cp /tests/show-config/conf.conf "$CONF" /usr/bin/odyssey "$CONF" sleep 1 console() { psql -h 127.0.0.1 -p 6432 -U console -d console \ --quiet --no-align --tuples-only -F '|' -c "$1" } value_of() { console 'show config' | awk -F '|' -v key="$1" '$1 == key { print $2 }' } changeable_of() { console 'show config' | awk -F '|' -v key="$1" '$1 == key { print $4 }' } expect() { local what="$1" got="$2" want="$3" if [ "$got" != "$want" ]; then echo "$what: got '$got', expected '$want'" >&2 cat /var/log/odyssey.log >&2 || true exit 1 fi } # the output is not empty test "$(console 'show config' | grep -c '|')" -gt 0 || { echo "show config returned no rows" >&2 exit 1 } # every row carries all four columns console 'show config' | awk -F '|' 'NF != 4 { print "unexpected column count: " $0 > "/dev/stderr"; exit 1 }' # changeable is a yes/no flag console 'show config' | awk -F '|' '$4 != "yes" && $4 != "no" { print "unexpected changeable value: " $0 > "/dev/stderr"; exit 1 }' # no value is left empty console 'show config' | awk -F '|' '$2 == "" || $3 == "" { print "empty value or default: " $0 > "/dev/stderr"; exit 1 }' # starting point, taken from conf.conf expect "log_debug value" "$(value_of log_debug)" "no" expect "log_debug changeable" "$(changeable_of log_debug)" "yes" expect "workers value" "$(value_of workers)" "2" expect "workers changeable" "$(changeable_of workers)" "no" # the file changes, RELOAD is not issued: the output must not follow sed -i 's/^log_debug no$/log_debug yes/' "$CONF" expect "log_debug before reload" "$(value_of log_debug)" "no" # RELOAD applies log_debug psql -h 127.0.0.1 -p 6432 -U console -d console -c 'reload' sleep 1 expect "log_debug after reload" "$(value_of log_debug)" "yes" # RELOAD does not apply workers: the process keeps the old value and the # output has to show that sed -i 's/^workers 2$/workers 8/' "$CONF" psql -h 127.0.0.1 -p 6432 -U console -d console -c 'reload' sleep 1 expect "workers after reload" "$(value_of workers)" "2" expect "workers changeable after reload" "$(changeable_of workers)" "no" # defaults come from od_config_init and do not depend on the file expect "workers default" \ "$(console 'show config' | awk -F '|' '$1 == "workers" { print $3 }')" "1" ody-stop odyssey-1.6.0-rc1/test/functional/tests/show-unix-socket-ports/000077500000000000000000000000001526064202200245565ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/show-unix-socket-ports/conf.conf000066400000000000000000000012161526064202200263520ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/var/run/postgresql" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" port 5432 } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/functional/tests/show-unix-socket-ports/runner.sh000066400000000000000000000023371526064202200264300ustar00rootroot00000000000000#!/bin/bash set -e /usr/bin/odyssey /tests/show-unix-socket-ports/conf.conf sleep 1 # Warm up: create some traffic so that servers/clients appear psql 'host=localhost port=6432 user=postgres dbname=postgres' \ --quiet --no-align --tuples-only -c 'SELECT 1' > /dev/null 2>&1 # show servers: columns are type,user,database,state,addr,port,local_addr,local_port,... psql 'host=localhost port=6432 user=postgres dbname=console' \ --quiet --no-align --tuples-only -F '|' -c 'show servers' | \ awk -F '|' 'NF>0 { port=$6; lport=$8; if (port !~ /^-?[0-9]+$/) { print "non-numeric servers.port: " port > "/dev/stderr"; exit 1 } if (lport !~ /^-?[0-9]+$/) { print "non-numeric servers.local_port: " lport > "/dev/stderr"; exit 1 } }' # show clients: columns are type,user,database,state,storage_user,addr,port,local_addr,local_port,... psql 'host=localhost port=6432 user=postgres dbname=console' \ --quiet --no-align --tuples-only -F '|' -c 'show clients' | \ awk -F '|' 'NF>0 { port=$7; lport=$9; if (port !~ /^-?[0-9]+$/) { print "non-numeric clients.port: " port > "/dev/stderr"; exit 1 } if (lport !~ /^-?[0-9]+$/) { print "non-numeric clients.local_port: " lport > "/dev/stderr"; exit 1 } }' ody-stopodyssey-1.6.0-rc1/test/functional/tests/tls-compat/000077500000000000000000000000001526064202200222455ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/tls-compat/config.conf000066400000000000000000000042521526064202200243640ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 tls "require" tls_key_file "/tests/tls-compat/server.key" tls_cert_file "/tests/tls-compat/server.pem" tls_ca_file "/tests/tls-compat/root.pem" tls_protocols "tlsv1.2" } listen { host "127.0.0.1" port 7432 tls "allow" tls_key_file "/tests/tls-compat/server.key" tls_cert_file "/tests/tls-compat/server.pem" tls_ca_file "/tests/tls-compat/root.pem" tls_protocols "tlsv1.2" } listen { host "127.0.0.1" port 8432 tls "prefer" tls_key_file "/tests/tls-compat/server.key" tls_cert_file "/tests/tls-compat/server.pem" tls_ca_file "/tests/tls-compat/root.pem" tls_protocols "tlsv1.2" } listen { host "127.0.0.1" port 9432 tls "verify_ca" tls_key_file "/tests/tls-compat/server.key" tls_cert_file "/tests/tls-compat/server.pem" tls_ca_file "/tests/tls-compat/root.pem" tls_protocols "tlsv1.2" } listen { host "127.0.0.1" port 10432 tls "verify_full" tls_key_file "/tests/tls-compat/server.key" tls_cert_file "/tests/tls-compat/server.pem" tls_ca_file "/tests/tls-compat/root.pem" tls_protocols "tlsv1.2" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "postgres" { user "postgres" { authentication "none" pool_routing "internal" storage "postgres_server" pool "session" } } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } database "auth_query_db" { user "auth_query_user_md5" { authentication "cert" auth_common_name default auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage "postgres_server" pool "session" } user "auth_query_user_scram_sha_256" { authentication "scram-sha-256" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_user "postgres" auth_query_db "postgres" storage "postgres_server" pool "session" } } odyssey-1.6.0-rc1/test/functional/tests/tls-compat/runner.sh000077500000000000000000000043701526064202200241210ustar00rootroot00000000000000#!/bin/bash -x set -e pushd /tests/tls-compat/ openssl genrsa -out root.key 2048 openssl req -new -key root.key -out root.csr -nodes -subj "/CN=odyssey-test-cn" openssl x509 -req -days 2 -in root.csr -signkey root.key -out root.pem openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -nodes -subj "/CN=localhost" openssl x509 -req -in server.csr -CA root.pem -CAkey root.key -CAcreateserial -out server.pem -days 2 openssl genrsa -out auth_query_user_md5.key 2048 openssl req -new -key auth_query_user_md5.key -out auth_query_user_md5.csr -nodes -subj "/CN=auth_query_user_md5" openssl x509 -req -in auth_query_user_md5.csr -CA root.pem -CAkey root.key -CAcreateserial -out auth_query_user_md5.pem -days 2 popd echo "ssl packages:" dpkg -l | grep ssl | cat /usr/bin/odyssey /tests/tls-compat/config.conf sleep 1 psql 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' && { echo "should not connect with ssl mode require" exit 1 } psql 'host=localhost port=9432 user=postgres dbname=postgres sslmode=disable' && { echo "should not connect with ssl mode verify-ca" exit 1 } psql 'host=localhost port=10432 user=postgres dbname=postgres sslmode=disable' && { echo "should not connect with ssl mode verify-full" exit 1 } # prefer and allow at listen section should accept both tls and non-tls connections psql 'host=localhost port=7432 user=postgres dbname=postgres sslmode=disable' || exit 1 psql 'host=localhost port=7432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/tls-compat/root.pem' || exit 1 psql 'host=localhost port=8432 user=postgres dbname=postgres sslmode=disable' || exit 1 psql 'host=localhost port=8432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/tls-compat/root.pem' || exit 1 # Check some read-only load will work with tls pgbench 'host=localhost port=6432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/tls-compat/root.pem' -j 2 -c 10 --select-only --no-vacuum --progress 1 -T 20 || exit 1 pgbench 'host=localhost port=6432 user=postgres dbname=postgres sslmode=verify-full sslrootcert=/tests/tls-compat/root.pem' -j 2 -c 10 --select-only --no-vacuum --progress 1 -T 20 --connect || exit 1 sleep 1 ody-stop || exit 1 odyssey-1.6.0-rc1/test/functional/tests/tls-reload-bad-cert/000077500000000000000000000000001526064202200237075ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/tls-reload-bad-cert/config.conf000066400000000000000000000012101526064202200260150ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 tls "allow" tls_key_file "/tests/tls-reload-bad-cert/good.key" tls_cert_file "/tests/tls-reload-bad-cert/good.pem" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } odyssey-1.6.0-rc1/test/functional/tests/tls-reload-bad-cert/runner.sh000066400000000000000000000025431526064202200255600ustar00rootroot00000000000000#!/usr/bin/env bash set -ex pushd /tests/tls-reload-bad-cert/ openssl req -x509 -newkey rsa:2048 -keyout good.key -out good.pem -days 2 -nodes -subj "/CN=odyssey-cert-good" # A second keypair, used to point the config at a key that does not match the # certificate. openssl req -x509 -newkey rsa:2048 -keyout other.key -out other.pem -days 2 -nodes -subj "/CN=odyssey-cert-other" popd served_cn() { echo | openssl s_client -starttls postgres -connect 127.0.0.1:6432 2>/dev/null | openssl x509 -noout -subject | sed -e 's/.*CN\s*=\s*//' } assert_serving_good_cert() { cn=$(served_cn) if [[ "$cn" != "odyssey-cert-good" ]]; then echo "$1: expected odyssey-cert-good, got '$cn'" cat /var/log/odyssey.log exit 1 fi } odyssey /tests/tls-reload-bad-cert/config.conf sleep 1 assert_serving_good_cert "before reload" # A certificate path that does not exist must not replace the working one. sed -i 's|good\.pem|missing.pem|; s|good\.key|missing.key|' /tests/tls-reload-bad-cert/config.conf kill -s HUP $(pidof odyssey) sleep 1 assert_serving_good_cert "after reload with a missing certificate" # Neither may a certificate that does not match its key. sed -i 's|missing\.pem|good.pem|; s|missing\.key|other.key|' /tests/tls-reload-bad-cert/config.conf kill -s HUP $(pidof odyssey) sleep 1 assert_serving_good_cert "after reload with a mismatched key" ody-stop odyssey-1.6.0-rc1/test/functional/tests/tls-reload-cert-path/000077500000000000000000000000001526064202200241155ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/tls-reload-cert-path/config.conf000066400000000000000000000012141526064202200262270ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 tls "allow" tls_key_file "/tests/tls-reload-cert-path/alpha.key" tls_cert_file "/tests/tls-reload-cert-path/alpha.pem" } storage "postgres_server" { type "remote" host "127.0.0.1" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } odyssey-1.6.0-rc1/test/functional/tests/tls-reload-cert-path/runner.sh000066400000000000000000000022271526064202200257650ustar00rootroot00000000000000#!/usr/bin/env bash set -ex pushd /tests/tls-reload-cert-path/ # Two certificates with distinct CNs, so that the one being served can be # identified from the wire. openssl req -x509 -newkey rsa:2048 -keyout alpha.key -out alpha.pem -days 2 -nodes -subj "/CN=odyssey-cert-alpha" openssl req -x509 -newkey rsa:2048 -keyout bravo.key -out bravo.pem -days 2 -nodes -subj "/CN=odyssey-cert-bravo" popd served_cn() { echo | openssl s_client -starttls postgres -connect 127.0.0.1:6432 2>/dev/null | openssl x509 -noout -subject | sed -e 's/.*CN\s*=\s*//' } odyssey /tests/tls-reload-cert-path/config.conf sleep 1 cn=$(served_cn) if [[ "$cn" != "odyssey-cert-alpha" ]]; then echo "expected odyssey-cert-alpha before reload, got '$cn'" cat /var/log/odyssey.log exit 1 fi # Point the listener at the other certificate, as a renewal that changes the # path would. sed -i 's|alpha\.key|bravo.key|; s|alpha\.pem|bravo.pem|' /tests/tls-reload-cert-path/config.conf kill -s HUP $(pidof odyssey) sleep 1 cn=$(served_cn) if [[ "$cn" != "odyssey-cert-bravo" ]]; then echo "expected odyssey-cert-bravo after reload, got '$cn'" cat /var/log/odyssey.log exit 1 fi ody-stop odyssey-1.6.0-rc1/test/functional/tests/transactional/000077500000000000000000000000001526064202200230245ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/transactional/f.sql000066400000000000000000000000271526064202200237710ustar00rootroot00000000000000\! sleep 2 select 42; odyssey-1.6.0-rc1/test/functional/tests/transactional/odyssey.conf000066400000000000000000000012371526064202200253750ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_ttl 5 } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/functional/tests/transactional/runner.sh000077500000000000000000000021451526064202200246760ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/transactional/odyssey.conf sleep 1 # ensure route paramaters cached psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select 42' # pool_ttl is 5, so the connections used to cache parameters was deallocation sleep 7 # ensure the parameters connection was deallocated count=`psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' -t | sed '/^$/d' | wc -l` if [ $count -ne 0 ]; then echo "expected one server but got" $count psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' exit 1 fi # connect, but run the query after 2 seconds psql 'host=localhost port=6432 user=postgres dbname=postgres' -f '/tests/transactional/f.sql' & to_kill=$! sleep 1 # there must be no connections - the query wasnt still sent count=`psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' -t | sed '/^$/d' | wc -l` if [ $count -ne 0 ]; then echo "expected one server but got" $count psql 'host=localhost port=6432 user=console dbname=console' -c 'show servers' exit 1 fi kill -9 $to_kill ody-stop odyssey-1.6.0-rc1/test/functional/tests/transactional_load/000077500000000000000000000000001526064202200240235ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/transactional_load/f.sql000066400000000000000000000000011526064202200247600ustar00rootroot00000000000000;odyssey-1.6.0-rc1/test/functional/tests/transactional_load/odyssey.conf000066400000000000000000000014741526064202200263770ustar00rootroot00000000000000daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/tmp" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no # strictly needed, to ensure connections pass free servers signal correctly workers 1 listen { host "127.0.0.1" port 6432 } storage "postgres_server" { type "remote" host "[127.0.0.1]:5432" } storage "local" { type "local" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "transaction" pool_ttl 5 pool_size 20 pool_timeout 1000 pool_notice_after_waiting_ms 500 } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/functional/tests/transactional_load/runner.sh000077500000000000000000000004741526064202200257000ustar00rootroot00000000000000#!/bin/bash -x # check that transactional load works set -exu /usr/bin/odyssey /tests/transactional_load/odyssey.conf sleep 1 # more connections than pool size pgbench 'host=localhost port=6432 user=postgres dbname=postgres' -f /tests/transactional_load/f.sql --no-vacuum -j 4 -c 40 -T 20 --progress 1 ody-stop odyssey-1.6.0-rc1/test/functional/tests/unix-socket-storage/000077500000000000000000000000001526064202200240755ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/functional/tests/unix-socket-storage/conf.conf000066400000000000000000000007661526064202200257020ustar00rootroot00000000000000storage "postgres_server" { type "remote" port 5432 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" } } daemonize yes pid_file "/var/run/odyssey.pid" unix_socket_dir "/var/run/postgresql" unix_socket_mode "0644" locks_dir "/tmp" log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_to_stdout no log_config yes log_debug no log_session yes log_stats no log_query no listen { host "127.0.0.1" port 6432 } odyssey-1.6.0-rc1/test/functional/tests/unix-socket-storage/runner.sh000077500000000000000000000004441526064202200257470ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/unix-socket-storage/conf.conf sleep 1 pgbench 'host=localhost port=6432 user=postgres dbname=postgres' -j 2 -c 10 --select-only --no-vacuum --progress 1 -T 5 || { echo "odyssey runned against pg unix socket doesn't work" exit 1 } ody-stop odyssey-1.6.0-rc1/test/jemalloc/000077500000000000000000000000001526064202200164445ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/jemalloc/Dockerfile000066400000000000000000000042401526064202200204360ustar00rootroot00000000000000FROM ubuntu:noble AS odyssey-build-env ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ openssl \ libpam0g-dev \ build-essential \ bison flex \ cmake FROM odyssey-build-env AS odyssey-build ARG odyssey_build_type RUN mkdir /build_dir WORKDIR /build_dir COPY . . RUN make clean && make ${odyssey_build_type} RUN cp /build_dir/build/sources/odyssey /odyssey COPY ./test/jemalloc/odyssey.conf /odyssey.conf FROM ubuntu:noble AS test-runner-env ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ build-essential \ autoconf \ postgresql-client postgresql-client-common postgresql-contrib \ python3 \ curl \ graphviz ghostscript ENV JEMALLOC_PATH=/jeamalloc-build RUN git clone --depth 1 https://github.com/jemalloc/jemalloc.git RUN cd jemalloc && ./autogen.sh --prefix=${JEMALLOC_PATH} --enable-prof && make -j $(nproc) && make install -j $(nproc) WORKDIR /jeamalloc-compat COPY --from=odyssey-build /odyssey /odyssey COPY --from=odyssey-build /odyssey.conf /odyssey.conf COPY --from=odyssey-build /build_dir /build_dir COPY ./test/jemalloc/entrypoint.sh ./ COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop ENTRYPOINT ["/jeamalloc-compat/entrypoint.sh"] odyssey-1.6.0-rc1/test/jemalloc/docker-compose.yml000066400000000000000000000010011526064202200220710ustar00rootroot00000000000000services: postgres: image: postgres:17 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres networks: - od_jemalloc_test_net runner: init: true build: dockerfile: ./test/jemalloc/Dockerfile context: ../../ # odyssey root dir args: odyssey_build_type: "${ODYSSEY_JEMALLOC_TEST_BUILD_TYPE:-build_release}" networks: - od_jemalloc_test_net networks: od_jemalloc_test_net: driver: bridge odyssey-1.6.0-rc1/test/jemalloc/entrypoint.sh000077500000000000000000000012401526064202200212130ustar00rootroot00000000000000#!/usr/bin/env bash set -eux until pg_isready -h postgres -p 5432 -U postgres -d postgres; do echo "Waiting for postgres to up..." sleep 1 done MALLOC_CONF=prof_leak:true,lg_prof_sample:0,prof_final:true LD_PRELOAD=${JEMALLOC_PATH}/lib/libjemalloc.so.2 /odyssey /odyssey.conf sleep 1 pgbench 'host=localhost port=6432 user=postgres dbname=postgres password=postgres' -i -s 20 pgbench 'host=localhost port=6432 user=postgres dbname=postgres password=postgres' -T 30 --progress 1 --no-vacuum -j 4 -c 50 --select-only ody-stop ${JEMALLOC_PATH}/bin/jeprof --show_bytes /odyssey jeprof.* ${JEMALLOC_PATH}/bin/jeprof --show_bytes --pdf /odyssey jeprof.* > /report.pdfodyssey-1.6.0-rc1/test/jemalloc/odyssey.conf000066400000000000000000000015241526064202200210140ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_debug no log_config yes log_session no log_query no log_stats yes workers 4 bindwith_reuseport yes log_file "/odyssey.log" stats_interval 60 listen { host "0.0.0.0" port 6432 } storage "local" { type "local" } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } user default { authentication "block" pool "session" storage "local" } } storage "postgres" { type "remote" host "postgres:5432" } database "postgres" { user "postgres" { authentication "scram-sha-256" password "postgres" storage "postgres" storage_password "postgres" pool "session" pool_size 50 } } odyssey-1.6.0-rc1/test/oom/000077500000000000000000000000001526064202200154505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/oom/Dockerfile000066400000000000000000000011051526064202200174370ustar00rootroot00000000000000FROM ubuntu:latest RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ postgresql-contrib \ sudo strace COPY ./test/oom/entrytest.sh /usr/local/bin/entrytest.sh RUN chmod +x /usr/local/bin/entrytest.sh ENTRYPOINT ["entrytest.sh"]odyssey-1.6.0-rc1/test/oom/docker-compose.yml000066400000000000000000000015721526064202200211120ustar00rootroot00000000000000services: primary: image: postgres:16 container_name: postgres environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - od_oom_test healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey environment: - PG_HOST=primary - LOG_CONFIG=yes - USER_AUTH_TYPE=none - RUN_MODE=test networks: - od_oom_test volumes: - ./odyssey.conf:/etc/odyssey/odyssey.conf depends_on: - primary privileged: true pid: "container:postgres" runner: build: dockerfile: ./test/oom/Dockerfile context: ../../ # odyssey root dir networks: - od_oom_test depends_on: - odyssey networks: od_oom_test: driver: bridge odyssey-1.6.0-rc1/test/oom/entrytest.sh000066400000000000000000000020641526064202200200470ustar00rootroot00000000000000#!/bin/bash set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 0.1 done until pg_isready -h odyssey -p 6432 -U postgres -d postgres; do echo "Wait for odyssey..." sleep 0.1 done psql 'host=odyssey port=6432 user=postgres dbname=postgres' -c 'select 1' || { echo "error: failed to make query" exit 1 } pgbench -i 'host=odyssey port=6432 user=postgres dbname=postgres' START_CLIENTS=5 MAX_CLIENTS=128 STEP=5 DURATION=10 echo "select repeat('a',1024*1024*50)" > /tmp/load.txt for ((c=START_CLIENTS; c<=MAX_CLIENTS; c+=STEP)) do pgbench 'host=odyssey port=6432 user=postgres dbname=postgres' -c $c -j $c -T $DURATION --progress 1 -f /tmp/load.txt -C >/tmp/pgbench.out 2>&1 || { echo "error: failed to run pgbench" output=$(cat /tmp/pgbench.out) echo "$output" if echo "$output" | grep -q "soft out of memory" ; then echo "OK: Soft oom found!" exit 0 fi exit 1 } output=$(cat /tmp/pgbench.out) echo "$output" done echo "ERROR: Soft oom not found" exit 1 odyssey-1.6.0-rc1/test/oom/odyssey.conf000066400000000000000000000011761526064202200200230ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_session no log_query no log_stats no daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "primary" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "session" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } } soft_oom { process "postgres" limit 2GB } odyssey-1.6.0-rc1/test/pg_regress/000077500000000000000000000000001526064202200170165ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/.dockerignore000066400000000000000000000000121526064202200214630ustar00rootroot00000000000000Dockerfileodyssey-1.6.0-rc1/test/pg_regress/Dockerfile000066400000000000000000000035501526064202200210130ustar00rootroot00000000000000FROM ubuntu:noble AS common-builder ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ openssl \ libpam0g-dev \ build-essential \ cmake \ bison flex \ pkg-config \ libreadline-dev \ clang \ libsystemd-dev \ libpthread-stubs0-dev libclang-rt-dev \ bison flex \ python3 FROM common-builder AS pg-build RUN git clone --depth 1 --branch REL_18_STABLE https://github.com/postgres/postgres.git RUN cd postgres && ./configure --prefix /usr --without-icu --without-zlib && make -j install >/dev/null RUN git clone --depth 1 https://github.com/pg-sharding/ttt /ttt RUN cd /ttt && make USE_PGXS=1 && make USE_PGXS=1 install FROM pg-build AS odyssey-build ARG build_type=build_dbg ARG odyssey_cc=gcc RUN mkdir /build_dir WORKDIR /build_dir COPY . . RUN CC=${odyssey_cc} make clean && make ${build_type} RUN cp /build_dir/build/sources/odyssey /usr/bin/odyssey COPY ./sources/machinarium/gdb/machinarium-gdb.py /gdb.py COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop FROM odyssey-build AS runner RUN useradd -ms /bin/bash postgres RUN mkdir regress WORKDIR /home/postgres COPY ./test/pg_regress/entrypoint.sh /home/postgres/entrypoint.sh COPY ./test/pg_regress/conf /home/postgres/regress/conf COPY ./test/pg_regress/tests /home/postgres/regress/tests COPY ./test/pg_regress/schedule /home/postgres/regress/schedule RUN chown -R postgres /home/postgres/regress RUN chown postgres /home/postgres/entrypoint.sh USER postgres ENTRYPOINT ["/home/postgres/entrypoint.sh"] odyssey-1.6.0-rc1/test/pg_regress/conf/000077500000000000000000000000001526064202200177435ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/conf/az_aware000066400000000000000000000032611526064202200214610ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config no log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes availability_zone "az1" listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "rr-az-aware" { type "remote" host "localhost:5432:az1,localhost:5433:az2,localhost:5434:az2" endpoints_status_poll_interval 5000 balancing { method "roundrobin" { az_aware yes } show_notice_messages yes } } storage "rr-az-unaware" { type "remote" host "localhost:5432:az1,localhost:5433:az2,localhost:5434:az2" endpoints_status_poll_interval 5000 balancing { method "roundrobin" { az_aware no } show_notice_messages yes } } database "postgres" { user "az-aware-user" { authentication "none" storage "rr-az-aware" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "az-unaware-user" { authentication "none" storage "rr-az-unaware" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "postgres" { authentication "none" storage "rr-az-aware" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yes odyssey-1.6.0-rc1/test/pg_regress/conf/balancer000066400000000000000000000031221526064202200214330ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config no log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "rr-hosts" { type "remote" host "localhost:5432,localhost:5433,localhost:5434" endpoints_status_poll_interval 5000 balancing { method "roundrobin" {} show_notice_messages yes } } storage "leastconn-hosts" { type "remote" host "localhost:5432,localhost:5433,localhost:5434" endpoints_status_poll_interval 5000 balancing { method "leastconn" {} show_notice_messages yes } } database "postgres" { user "rruser" { authentication "none" storage "rr-hosts" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "leastconnuser" { authentication "none" storage "leastconn-hosts" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "postgres" { authentication "none" storage "leastconn-hosts" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yesodyssey-1.6.0-rc1/test/pg_regress/conf/broken_conn000066400000000000000000000017311526064202200221650ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "localhost:5432" endpoints_status_poll_interval 5000 } database "postgres" { user "tuser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_discard no pool_smart_discard no } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yesodyssey-1.6.0-rc1/test/pg_regress/conf/listen_balancer000066400000000000000000000027741526064202200230250ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config no log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes listen { host "*" port 6432 backlog 128 tls "disable" } listen { host "*" port 7432 backlog 128 tls "disable" balancing { method "leastconn" {} show_notice_messages yes } } storage "rr-hosts" { type "remote" host "localhost:5432,localhost:5433,localhost:5434" endpoints_status_poll_interval 5000 balancing { method "roundrobin" {} show_notice_messages yes } } database "postgres" { user "rruser" { authentication "none" storage "rr-hosts" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "lcuser" { authentication "none" storage "rr-hosts" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "postgres" { authentication "none" storage "rr-hosts" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yes odyssey-1.6.0-rc1/test/pg_regress/conf/pin_on_listen000066400000000000000000000017321526064202200225310ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "localhost:5432" endpoints_status_poll_interval 5000 } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_pin_on_listen yes } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yesodyssey-1.6.0-rc1/test/pg_regress/conf/simple000066400000000000000000000062071526064202200211640ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config yes log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes listen { host "*" port 6432 backlog 128 compression yes tls "disable" } storage "postgres_server" { type "remote" host "localhost:5432" endpoints_status_poll_interval 5000 } database "postgres" { user "suser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "session" pool_size 32 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes pool_client_idle_timeout 4 pool_idle_in_transaction_timeout 5 client_fwd_error yes client_show_id no application_name_add_host yes server_lifetime 1901 log_debug no client_max 200000 } user "tuser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes pool_client_idle_timeout 4 pool_idle_in_transaction_timeout 5 client_fwd_error yes client_show_id no application_name_add_host yes server_lifetime 1901 log_debug no client_max 200000 pool_pin_on_listen yes } user "stuser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "statement" pool_size 32 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes pool_client_idle_timeout 4 pool_idle_in_transaction_timeout 5 client_fwd_error yes client_show_id no application_name_add_host yes server_lifetime 1901 log_debug no client_max 200000 } } database "broken_collation" { user "suser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "session" pool_size 32 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes pool_client_idle_timeout 4 pool_idle_in_transaction_timeout 5 client_show_id no client_fwd_error yes application_name_add_host yes server_lifetime 1901 log_debug no client_max 200000 } user "tuser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 0 pool_ttl 1201 pool_discard yes pool_cancel yes pool_rollback yes pool_reserve_prepared_statement yes pool_client_idle_timeout 4 pool_idle_in_transaction_timeout 20000 client_fwd_error yes client_show_id no application_name_add_host yes server_lifetime 1901 log_debug no client_max 200000 pool_pin_on_listen yes } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yesodyssey-1.6.0-rc1/test/pg_regress/conf/tsa000066400000000000000000000045231526064202200204610ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug no log_config no log_session no log_query no log_stats no log_file "/tmp/odyssey.log" stats_interval 60 workers 1 resolvers 1 readahead 4096 cache_coroutine 0 nodelay yes keepalive 15 keepalive_keep_interval 75 keepalive_probes 9 keepalive_usr_timeout 0 virtual_processing yes listen { host "*" port 6432 backlog 128 compression yes tls "disable" } listen { host "*" port 7432 backlog 128 compression yes tls "disable" target_session_attrs "read-only" } listen { host "*" port 8432 backlog 128 compression yes tls "disable" target_session_attrs "read-write" } storage "postgres_server" { type "remote" host "localhost:5432,localhost:5433,localhost:5434" endpoints_status_poll_interval 5000 balancing { method "roundrobin" {} show_notice_messages yes } } storage "only_rw_alive" { type "remote" host "localhost:5432,localhost:5430" endpoints_status_poll_interval 5000 balancing { method "roundrobin" {} show_notice_messages yes } } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no } user "rouser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no target_session_attrs "read-only" } user "rwuser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no target_session_attrs "read-write" } user "preferstandbyuser-rw" { authentication "none" storage "only_rw_alive" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no target_session_attrs "prefer-standby" } user "preferstandbyuser-ro" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_size 32 pool_timeout 1000 client_show_id no target_session_attrs "prefer-standby" } } storage "local" { type "local" } database "console" { user default { authentication "none" role "admin" pool "session" storage "local" } } bindwith_reuseport yesodyssey-1.6.0-rc1/test/pg_regress/entrypoint.sh000077500000000000000000000046161526064202200215770ustar00rootroot00000000000000#!/usr/bin/env bash set -eux PG_REGRESS=$(pg_config --libdir)/postgresql/pgxs/src/test/regress/pg_regress REGRESS_DIR=/home/postgres/regress PGDIR=/home/postgres/pgdir PGDIRREPL1=/home/postgres/pgrepl1 PGDIRREPL2=/home/postgres/pgrepl2 do_test() { local name=$1 local port=$2 local user=$3 export ASAN_OPTIONS="log_path=/asan-output.log fast_unwind_on_malloc=0" export TSAN_OPTIONS="log_path=/tsan-output.log" odyssey ${REGRESS_DIR}/conf/${name} local it=0 until (( it == 50 )) || pg_isready -h localhost -p ${port} -U postgres -d postgres; do echo "Wait for odyssey..." echo "$(( it++ ))" sleep 0.1 done (( it < 50 )) PGTEST_PORT="${port}" PGTEST_DIR="${REGRESS_DIR}/tests/${name}" PGTEST_USER="${user}" PGTEST_DB=postgres ${PG_REGRESS} \ --debug \ --host=localhost \ --port=${port} \ --user=${user} \ --dbname=postgres \ --use-existing \ --schedule=${REGRESS_DIR}/schedule/${name} \ --inputdir=${REGRESS_DIR}/tests/${name} \ --outputdir=${REGRESS_DIR}/tests/${name} || { echo "====== DIFFS =======" cat ${REGRESS_DIR}/tests/${name}/regression.diffs || true echo "====== OUT =======" cat ${REGRESS_DIR}/tests/${name}/regression.out || true ody-stop exit 1 } ody-stop } pg_ctl stop -D ${PGDIRREPL1} || true pg_ctl stop -D ${PGDIRREPL2} || true pg_ctl stop -D ${PGDIR} || true rm -rf ${PGDIR} || true rm -rf ${PGDIRREPL1} || true rm -rf ${PGDIRREPL2} || true initdb -D ${PGDIR} echo "session_preload_libraries = 'transaction_transients_trace'" >> ${PGDIR}/postgresql.conf pg_ctl start -D ${PGDIR} -l ${PGDIR}/log.txt psql -p 5432 -c "ALTER SYSTEM SET wal_level = replica;" psql -p 5432 -c "ALTER SYSTEM SET max_wal_senders = 10;" pg_ctl restart -D ${PGDIR} pg_basebackup -D ${PGDIRREPL1} -p 5432 -R echo "port = 5433" >> ${PGDIRREPL1}/postgresql.conf pg_ctl start -D ${PGDIRREPL1} -l ${PGDIRREPL1}/log.txt pg_basebackup -D ${PGDIRREPL2} -p 5432 -R echo "port = 5434" >> ${PGDIRREPL2}/postgresql.conf pg_ctl start -D ${PGDIRREPL2} -l ${PGDIRREPL2}/log.txt do_test pin_on_listen 6432 postgres do_test simple 6432 suser do_test simple 6432 tuser do_test broken_conn 6432 tuser do_test balancer 6432 postgres do_test listen_balancer 6432 postgres do_test tsa 6432 postgres do_test az_aware 6432 postgres odyssey-1.6.0-rc1/test/pg_regress/schedule/000077500000000000000000000000001526064202200206125ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/schedule/az_aware000066400000000000000000000000461526064202200223260ustar00rootroot00000000000000test: az_aware_rr test: az_unaware_rr odyssey-1.6.0-rc1/test/pg_regress/schedule/balancer000066400000000000000000000000401526064202200222760ustar00rootroot00000000000000test: roundrobin test: leastconnodyssey-1.6.0-rc1/test/pg_regress/schedule/broken_conn000066400000000000000000000000231526064202200230250ustar00rootroot00000000000000test: server_closedodyssey-1.6.0-rc1/test/pg_regress/schedule/listen_balancer000066400000000000000000000000701526064202200236570ustar00rootroot00000000000000test: listen_rr_default test: listen_leastconn_override odyssey-1.6.0-rc1/test/pg_regress/schedule/pin_on_listen000066400000000000000000000000161526064202200233720ustar00rootroot00000000000000test: unlistenodyssey-1.6.0-rc1/test/pg_regress/schedule/simple000066400000000000000000000003241526064202200220250ustar00rootroot00000000000000test: startup_notice test: application_name test: broken_cached_plan test: idle_in_transaction test: virtual_show test: virtual_pin_backend test: ttt_pin_backend test: virtual_pooling_mode test: show_config_fieldodyssey-1.6.0-rc1/test/pg_regress/schedule/tsa000066400000000000000000000000271526064202200213230ustar00rootroot00000000000000test: ports test: usersodyssey-1.6.0-rc1/test/pg_regress/tests/000077500000000000000000000000001526064202200201605ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/000077500000000000000000000000001526064202200217515ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/expected/000077500000000000000000000000001526064202200235525ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/expected/az_aware_rr.out000066400000000000000000000011651526064202200266020ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=az-aware-user dbname=postgres' select 1; NOTICE: select host tcp://localhost:5432 ?column? ---------- 1 (1 row) select 2; NOTICE: select host tcp://localhost:5432 ?column? ---------- 2 (1 row) select 3; NOTICE: select host tcp://localhost:5432 ?column? ---------- 3 (1 row) select 4; NOTICE: select host tcp://localhost:5432 ?column? ---------- 4 (1 row) select 5; NOTICE: select host tcp://localhost:5432 ?column? ---------- 5 (1 row) select 6; NOTICE: select host tcp://localhost:5432 ?column? ---------- 6 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/expected/az_unaware_rr.out000066400000000000000000000011671526064202200271470ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=az-unaware-user dbname=postgres' select 1; NOTICE: select host tcp://localhost:5433 ?column? ---------- 1 (1 row) select 2; NOTICE: select host tcp://localhost:5434 ?column? ---------- 2 (1 row) select 3; NOTICE: select host tcp://localhost:5432 ?column? ---------- 3 (1 row) select 4; NOTICE: select host tcp://localhost:5433 ?column? ---------- 4 (1 row) select 5; NOTICE: select host tcp://localhost:5434 ?column? ---------- 5 (1 row) select 6; NOTICE: select host tcp://localhost:5432 ?column? ---------- 6 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/sql/000077500000000000000000000000001526064202200225505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/sql/az_aware_rr.sql000066400000000000000000000002111526064202200255570ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=az-aware-user dbname=postgres' select 1; select 2; select 3; select 4; select 5; select 6; odyssey-1.6.0-rc1/test/pg_regress/tests/az_aware/sql/az_unaware_rr.sql000066400000000000000000000002131526064202200261240ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=az-unaware-user dbname=postgres' select 1; select 2; select 3; select 4; select 5; select 6; odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/000077500000000000000000000000001526064202200217275ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/expected/000077500000000000000000000000001526064202200235305ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/expected/leastconn.out000066400000000000000000000016251526064202200262530ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=leastconnuser dbname=postgres' select 42; NOTICE: select host tcp://localhost:5433 ?column? ---------- 42 (1 row) select 43; NOTICE: select host tcp://localhost:5434 ?column? ---------- 43 (1 row) select 44; NOTICE: select host tcp://localhost:5432 ?column? ---------- 44 (1 row) select 45; NOTICE: select host tcp://localhost:5432 ?column? ---------- 45 (1 row) select 46; NOTICE: select host tcp://localhost:5432 ?column? ---------- 46 (1 row) select 47; NOTICE: select host tcp://localhost:5432 ?column? ---------- 47 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/expected/roundrobin.out000066400000000000000000000016161526064202200264460ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=rruser dbname=postgres' select 42; NOTICE: select host tcp://localhost:5433 ?column? ---------- 42 (1 row) select 43; NOTICE: select host tcp://localhost:5434 ?column? ---------- 43 (1 row) select 44; NOTICE: select host tcp://localhost:5432 ?column? ---------- 44 (1 row) select 45; NOTICE: select host tcp://localhost:5433 ?column? ---------- 45 (1 row) select 46; NOTICE: select host tcp://localhost:5434 ?column? ---------- 46 (1 row) select 47; NOTICE: select host tcp://localhost:5432 ?column? ---------- 47 (1 row) select 67; NOTICE: select host tcp://localhost:5433 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5434 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/sql/000077500000000000000000000000001526064202200225265ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/sql/leastconn.sql000066400000000000000000000002621526064202200252350ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=leastconnuser dbname=postgres' select 42; select 43; select 44; select 45; select 46; select 47; select 67; select 67; select 67;odyssey-1.6.0-rc1/test/pg_regress/tests/balancer/sql/roundrobin.sql000066400000000000000000000002531526064202200254300ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=rruser dbname=postgres' select 42; select 43; select 44; select 45; select 46; select 47; select 67; select 67; select 67;odyssey-1.6.0-rc1/test/pg_regress/tests/broken_conn/000077500000000000000000000000001526064202200224555ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/broken_conn/expected/000077500000000000000000000000001526064202200242565ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/broken_conn/expected/server_closed.out000066400000000000000000000003641526064202200276510ustar00rootroot00000000000000select 43; ?column? ---------- 43 (1 row) set application_name to 'kill_me'; \! ${PGTEST_DIR}/sql/kill_server.sh pg_terminate_backend ---------------------- t (1 row) \! sleep 1 select 87; ?column? ---------- 87 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/broken_conn/sql/000077500000000000000000000000001526064202200232545ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/broken_conn/sql/kill_server.sh000077500000000000000000000002661526064202200261400ustar00rootroot00000000000000#!/usr/bin/env bash psql "host=localhost port=5432 user=postgres dbname=postgres" -c \ "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE application_name='kill_me'" odyssey-1.6.0-rc1/test/pg_regress/tests/broken_conn/sql/server_closed.sql000066400000000000000000000001541526064202200266340ustar00rootroot00000000000000select 43; set application_name to 'kill_me'; \! ${PGTEST_DIR}/sql/kill_server.sh \! sleep 1 select 87; odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/000077500000000000000000000000001526064202200233055ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/expected/000077500000000000000000000000001526064202200251065ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/expected/listen_leastconn_override.out000066400000000000000000000016161526064202200331060ustar00rootroot00000000000000\connect 'host=localhost port=7432 user=lcuser dbname=postgres' select 42; NOTICE: select host tcp://localhost:5433 ?column? ---------- 42 (1 row) select 43; NOTICE: select host tcp://localhost:5434 ?column? ---------- 43 (1 row) select 44; NOTICE: select host tcp://localhost:5432 ?column? ---------- 44 (1 row) select 45; NOTICE: select host tcp://localhost:5432 ?column? ---------- 45 (1 row) select 46; NOTICE: select host tcp://localhost:5432 ?column? ---------- 46 (1 row) select 47; NOTICE: select host tcp://localhost:5432 ?column? ---------- 47 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/expected/listen_rr_default.out000066400000000000000000000016161526064202200313500ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=rruser dbname=postgres' select 42; NOTICE: select host tcp://localhost:5434 ?column? ---------- 42 (1 row) select 43; NOTICE: select host tcp://localhost:5432 ?column? ---------- 43 (1 row) select 44; NOTICE: select host tcp://localhost:5433 ?column? ---------- 44 (1 row) select 45; NOTICE: select host tcp://localhost:5434 ?column? ---------- 45 (1 row) select 46; NOTICE: select host tcp://localhost:5432 ?column? ---------- 46 (1 row) select 47; NOTICE: select host tcp://localhost:5433 ?column? ---------- 47 (1 row) select 67; NOTICE: select host tcp://localhost:5434 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5432 ?column? ---------- 67 (1 row) select 67; NOTICE: select host tcp://localhost:5433 ?column? ---------- 67 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/sql/000077500000000000000000000000001526064202200241045ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/sql/listen_leastconn_override.sql000066400000000000000000000002441526064202200320700ustar00rootroot00000000000000\connect 'host=localhost port=7432 user=lcuser dbname=postgres' select 42; select 43; select 44; select 45; select 46; select 47; select 67; select 67; select 67; odyssey-1.6.0-rc1/test/pg_regress/tests/listen_balancer/sql/listen_rr_default.sql000066400000000000000000000002441526064202200303320ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=rruser dbname=postgres' select 42; select 43; select 44; select 45; select 46; select 47; select 67; select 67; select 67; odyssey-1.6.0-rc1/test/pg_regress/tests/pin_on_listen/000077500000000000000000000000001526064202200230205ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/pin_on_listen/expected/000077500000000000000000000000001526064202200246215ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/pin_on_listen/expected/unlisten.out000066400000000000000000000023771526064202200272240ustar00rootroot00000000000000select 42; ?column? ---------- 42 (1 row) show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) listen a; show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) unlisten b; show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) unlisten *; show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) listen b; show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) unlisten * \parse unlisten_all_stmt \bind_named unlisten_all_stmt \g show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) listen c \parse listen_stmt \bind_named listen_stmt \g show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) unlisten * \parse unlisten_all_stmt2 \bind_named unlisten_all_stmt2 \g show odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/pin_on_listen/sql/000077500000000000000000000000001526064202200236175ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/pin_on_listen/sql/unlisten.sql000066400000000000000000000007311526064202200262020ustar00rootroot00000000000000select 42; show odyssey.pin_backend; listen a; show odyssey.pin_backend; unlisten b; show odyssey.pin_backend; unlisten *; show odyssey.pin_backend; listen b; show odyssey.pin_backend; unlisten * \parse unlisten_all_stmt \bind_named unlisten_all_stmt \g show odyssey.pin_backend; listen c \parse listen_stmt \bind_named listen_stmt \g show odyssey.pin_backend; unlisten * \parse unlisten_all_stmt2 \bind_named unlisten_all_stmt2 \g show odyssey.pin_backend; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/000077500000000000000000000000001526064202200214515ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/000077500000000000000000000000001526064202200232525ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/application_name.out000066400000000000000000000007521526064202200273120ustar00rootroot00000000000000SELECT 'z'; ?column? ---------- z (1 row) SET application_name to 'AAA'; SHOW application_name; application_name ------------------ AAA - ::1 (1 row) SET application_name to 'This-is-long-application-name-that-must-be-cut-to-add-host-and-this-is-tail'; SHOW application_name; application_name ----------------------------------------------------------------- This-is-long-application-name-that-must-be-cut-to-add-hos - ::1 (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/broken_cached_plan.out000066400000000000000000000005521526064202200275660ustar00rootroot00000000000000drop table if exists zsb; NOTICE: table "zsb" does not exist, skipping create table zsb (id int); select * from zsb \parse pstmt \bind_named pstmt \g id ---- (0 rows) alter table zsb alter column id type float; \bind_named pstmt \g ERROR: cached plan must not change result type select 42; ?column? ---------- 42 (1 row) drop table if exists zsb; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/idle_in_transaction.out000066400000000000000000000016351526064202200300200ustar00rootroot00000000000000\! psql --echo-all --no-psqlrc --quiet -p ${PGTEST_PORT} -h localhost -U ${PGTEST_USER} -d ${PGTEST_DB} -f ${PGTEST_DIR}/sql/idle_in_transaction_body.sql 2>&1; true begin; select 53; ?column? ---------- 53 (1 row) \! sleep 1 select 73; ?column? ---------- 73 (1 row) -- long op is not broken select pg_sleep(6); pg_sleep ---------- (1 row) \! sleep 6 select 76; psql:/home/postgres/regress/tests/simple/sql/idle_in_transaction_body.sql:13: FATAL: odyssey: Odyssey has dropped the connection DETAIL: Connection was dropped by Odyssey due to idle in transaction timeout psql:/home/postgres/regress/tests/simple/sql/idle_in_transaction_body.sql:13: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. psql:/home/postgres/regress/tests/simple/sql/idle_in_transaction_body.sql:13: error: connection to server was lost odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/show_config_field.out000066400000000000000000000013371526064202200274570ustar00rootroot00000000000000\c console SHOW CONFIG availability_zone; key | value | default | changeable -------------------+-----------+-----------+------------ availability_zone | (not set) | (not set) | no (1 row) SHOW CONFIG log_debug; key | value | default | changeable -----------+-------+---------+------------ log_debug | no | no | yes (1 row) SHOW CONFIG log_format; key | value | default | changeable ------------+----------------------------+-----------+------------ log_format | %p %t %l [%i %s] (%c) %m\n | (not set) | no (1 row) SHOW CONFIG workers; key | value | default | changeable ---------+-------+---------+------------ workers | 1 | 1 | no (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/startup_notice.out000066400000000000000000000012741526064202200270520ustar00rootroot00000000000000DROP DATABASE IF EXISTS broken_collation; NOTICE: database "broken_collation" does not exist, skipping CREATE DATABASE broken_collation; UPDATE pg_database SET datcollversion = 'bogus-version' WHERE datname = 'broken_collation'; \connect broken_collation WARNING: database "broken_collation" has no actual collation version, but a version was recorded select 42; ?column? ---------- 42 (1 row) \connect postgres -- drop connections to avoid error of 'There is 1 other session using the database.' SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'broken_collation'; pg_terminate_backend ---------------------- t (1 row) DROP DATABASE IF EXISTS broken_collation; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/ttt_pin_backend.out000066400000000000000000000014331526064202200271340ustar00rootroot00000000000000SELECT 1; ?column? ---------- 1 (1 row) SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) BEGIN; CREATE TEMP TABLE z_ttt(i int); SELECT 1; ?column? ---------- 1 (1 row) SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) COMMIT; INSERT INTO z_ttt VALUES (42); SELECT 1; ?column? ---------- 1 (1 row) SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) DROP TABLE z_ttt; SELECT 1; ?column? ---------- 1 (1 row) SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/virtual_pin_backend.out000066400000000000000000000031311526064202200300040ustar00rootroot00000000000000SELECT 1; ?column? ---------- 1 (1 row) SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) SET odyssey.pin_backend = "on"; NOTICE: processed virtually by odyssey SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) SET odyssey.pin_backend TO 'off'; NOTICE: processed virtually by odyssey SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) SET odyssey.pin_backend = "true"; NOTICE: processed virtually by odyssey SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) SET odyssey.pin_backend TO 'false'; NOTICE: processed virtually by odyssey SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) SET odyssey.pin_backend = "1"; NOTICE: processed virtually by odyssey SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- on (1 row) SET odyssey.pin_backend TO '0'; NOTICE: processed virtually by odyssey SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) SET odyssey.pin_backend = "invalid"; NOTICE: processed virtually by odyssey ERROR: invalid value for parameter "odyssey.pin_backend": "invalid" SHOW odyssey.pin_backend; NOTICE: processed virtually by odyssey odyssey.pin_backend --------------------- off (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/virtual_pooling_mode.out000066400000000000000000000022501526064202200302230ustar00rootroot00000000000000\set VERBOSITY verbose \c postgres suser SHOW odyssey.pooling_mode; NOTICE: processed virtually by odyssey odyssey.pooling_mode ---------------------- session (1 row) SET odyssey.pooling_mode = 'transaction'; NOTICE: processed virtually by odyssey ERROR: 55P02: parameter "odyssey.pooling_mode" cannot be changed now SHOW odyssey.pooling_mode; NOTICE: processed virtually by odyssey odyssey.pooling_mode ---------------------- session (1 row) \c postgres tuser SHOW odyssey.pooling_mode; NOTICE: processed virtually by odyssey odyssey.pooling_mode ---------------------- transaction (1 row) SET odyssey.pooling_mode = 'session'; NOTICE: processed virtually by odyssey ERROR: 55P02: parameter "odyssey.pooling_mode" cannot be changed now SHOW odyssey.pooling_mode; NOTICE: processed virtually by odyssey odyssey.pooling_mode ---------------------- transaction (1 row) \c postgres stuser SHOW odyssey.pooling_mode; NOTICE: processed virtually by odyssey odyssey.pooling_mode ---------------------- statement (1 row) \c postgres suser SHOW odyssey.pooling_mode; NOTICE: processed virtually by odyssey odyssey.pooling_mode ---------------------- session (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/simple/expected/virtual_show.out000066400000000000000000000050471526064202200265370ustar00rootroot00000000000000SELECT 1; ?column? ---------- 1 (1 row) SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) SET odyssey.target_session_attrs = "read-only"; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ read-only (1 row) SHOW "odyssey.target_session_attrs"; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ read-only (1 row) SET odyssey.target_session_attrs TO 'read-write'; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ read-write (1 row) SET odyssey.target_session_attrs = "prefer-standby"; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ prefer-standby (1 row) SET odyssey.target_session_attrs TO 'any'; NOTICE: processed virtually by odyssey SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) SET odyssey.target_session_attrs TO 'z'; NOTICE: processed virtually by odyssey ERROR: invalid value for parameter "odyssey.target_session_attrs": "z" SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) SET odyssey.target_session_attrs TO 'read-onl'; NOTICE: processed virtually by odyssey ERROR: invalid value for parameter "odyssey.target_session_attrs": "read-onl" SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) SET odyssey.target_session_attrs TO 'read-only-foo'; NOTICE: processed virtually by odyssey ERROR: invalid value for parameter "odyssey.target_session_attrs": "read-only-foo" SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) SET odyssey.target_session_attrs TO 'an'; NOTICE: processed virtually by odyssey ERROR: invalid value for parameter "odyssey.target_session_attrs": "an" SHOW odyssey.target_session_attrs; NOTICE: processed virtually by odyssey odyssey.target_session_attrs ------------------------------ any (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/000077500000000000000000000000001526064202200222505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/application_name.sql000066400000000000000000000003011526064202200262660ustar00rootroot00000000000000SELECT 'z'; SET application_name to 'AAA'; SHOW application_name; SET application_name to 'This-is-long-application-name-that-must-be-cut-to-add-host-and-this-is-tail'; SHOW application_name;odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/broken_cached_plan.sql000066400000000000000000000003251526064202200265520ustar00rootroot00000000000000drop table if exists zsb; create table zsb (id int); select * from zsb \parse pstmt \bind_named pstmt \g alter table zsb alter column id type float; \bind_named pstmt \g select 42; drop table if exists zsb; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/idle_in_transaction.sql000066400000000000000000000002451526064202200270020ustar00rootroot00000000000000\! psql --echo-all --no-psqlrc --quiet -p ${PGTEST_PORT} -h localhost -U ${PGTEST_USER} -d ${PGTEST_DB} -f ${PGTEST_DIR}/sql/idle_in_transaction_body.sql 2>&1; true odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/idle_in_transaction_body.sql000066400000000000000000000001711526064202200300150ustar00rootroot00000000000000begin; select 53; \! sleep 1 select 73; -- long op is not broken select pg_sleep(6); \! sleep 6 select 76; commit; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/show_config_field.sql000066400000000000000000000001561526064202200264430ustar00rootroot00000000000000\c console SHOW CONFIG availability_zone; SHOW CONFIG log_debug; SHOW CONFIG log_format; SHOW CONFIG workers; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/startup_notice.sql000066400000000000000000000006751526064202200260440ustar00rootroot00000000000000DROP DATABASE IF EXISTS broken_collation; CREATE DATABASE broken_collation; UPDATE pg_database SET datcollversion = 'bogus-version' WHERE datname = 'broken_collation'; \connect broken_collation select 42; \connect postgres -- drop connections to avoid error of 'There is 1 other session using the database.' SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'broken_collation'; DROP DATABASE IF EXISTS broken_collation; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/ttt_pin_backend.sql000066400000000000000000000003641526064202200261240ustar00rootroot00000000000000SELECT 1; SHOW odyssey.pin_backend; BEGIN; CREATE TEMP TABLE z_ttt(i int); SELECT 1; SHOW odyssey.pin_backend; COMMIT; INSERT INTO z_ttt VALUES (42); SELECT 1; SHOW odyssey.pin_backend; DROP TABLE z_ttt; SELECT 1; SHOW odyssey.pin_backend; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/virtual_pin_backend.sql000066400000000000000000000007171526064202200270010ustar00rootroot00000000000000 SELECT 1; SHOW odyssey.pin_backend; SET odyssey.pin_backend = "on"; SHOW odyssey.pin_backend; SET odyssey.pin_backend TO 'off'; SHOW odyssey.pin_backend; SET odyssey.pin_backend = "true"; SHOW odyssey.pin_backend; SET odyssey.pin_backend TO 'false'; SHOW odyssey.pin_backend; SET odyssey.pin_backend = "1"; SHOW odyssey.pin_backend; SET odyssey.pin_backend TO '0'; SHOW odyssey.pin_backend; SET odyssey.pin_backend = "invalid"; SHOW odyssey.pin_backend; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/virtual_pooling_mode.sql000066400000000000000000000005311526064202200272110ustar00rootroot00000000000000 \set VERBOSITY verbose \c postgres suser SHOW odyssey.pooling_mode; SET odyssey.pooling_mode = 'transaction'; SHOW odyssey.pooling_mode; \c postgres tuser SHOW odyssey.pooling_mode; SET odyssey.pooling_mode = 'session'; SHOW odyssey.pooling_mode; \c postgres stuser SHOW odyssey.pooling_mode; \c postgres suser SHOW odyssey.pooling_mode; odyssey-1.6.0-rc1/test/pg_regress/tests/simple/sql/virtual_show.sql000066400000000000000000000013561526064202200255240ustar00rootroot00000000000000 SELECT 1; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs = "read-only"; SHOW odyssey.target_session_attrs; SHOW "odyssey.target_session_attrs"; SET odyssey.target_session_attrs TO 'read-write'; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs = "prefer-standby"; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'any'; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'z'; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'read-onl'; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'read-only-foo'; SHOW odyssey.target_session_attrs; SET odyssey.target_session_attrs TO 'an'; SHOW odyssey.target_session_attrs;odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/000077500000000000000000000000001526064202200207475ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/expected/000077500000000000000000000000001526064202200225505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/expected/ports.out000066400000000000000000000033001526064202200244440ustar00rootroot00000000000000\connect 'host=localhost port=7432 user=postgres dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5434 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) \connect 'host=localhost port=8432 user=postgres dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) \connect 'host=localhost port=7432 user=postgres dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5434 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5434 pg_is_in_recovery ------------------- t (1 row) \connect 'host=localhost port=8432 user=postgres dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/expected/users.out000066400000000000000000000033241526064202200244440ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=rouser dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5434 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) \connect 'host=localhost port=6432 user=rwuser dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) \connect 'host=localhost port=6432 user=preferstandbyuser-rw dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5432 pg_is_in_recovery ------------------- f (1 row) \connect 'host=localhost port=6432 user=preferstandbyuser-ro dbname=postgres' select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5434 pg_is_in_recovery ------------------- t (1 row) select pg_is_in_recovery(); NOTICE: select host tcp://localhost:5433 pg_is_in_recovery ------------------- t (1 row) odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/sql/000077500000000000000000000000001526064202200215465ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/sql/ports.sql000066400000000000000000000011471526064202200234410ustar00rootroot00000000000000\connect 'host=localhost port=7432 user=postgres dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); \connect 'host=localhost port=8432 user=postgres dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); \connect 'host=localhost port=7432 user=postgres dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); \connect 'host=localhost port=8432 user=postgres dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); odyssey-1.6.0-rc1/test/pg_regress/tests/tsa/sql/users.sql000066400000000000000000000011731526064202200234320ustar00rootroot00000000000000\connect 'host=localhost port=6432 user=rouser dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); \connect 'host=localhost port=6432 user=rwuser dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); \connect 'host=localhost port=6432 user=preferstandbyuser-rw dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); \connect 'host=localhost port=6432 user=preferstandbyuser-ro dbname=postgres' select pg_is_in_recovery(); select pg_is_in_recovery(); select pg_is_in_recovery(); odyssey-1.6.0-rc1/test/prom-exporter/000077500000000000000000000000001526064202200175015ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/prom-exporter/Dockerfile000066400000000000000000000005051526064202200214730ustar00rootroot00000000000000FROM golang:1.27.0 RUN apt-get update -y && apt-get install -y postgresql-client postgresql-contrib RUN mkdir -p /test WORKDIR /test COPY ./prometheus/exporter . COPY ./test/prom-exporter/entrypoint.sh . COPY ./test/prom-exporter/expected.out . RUN go mod tidy && go build -o exporter ENTRYPOINT ["/test/entrypoint.sh"]odyssey-1.6.0-rc1/test/prom-exporter/docker-compose.yml000066400000000000000000000013251526064202200231370ustar00rootroot00000000000000services: primary: image: postgres:16 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - prom_exporter_net healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey volumes: - ./test.conf:/etc/odyssey/odyssey.conf networks: - prom_exporter_net depends_on: - primary tester: build: context: ../../ # odyssey root dir dockerfile: ./test/prom-exporter/Dockerfile networks: - prom_exporter_net depends_on: - odyssey networks: prom_exporter_net: driver: bridge odyssey-1.6.0-rc1/test/prom-exporter/entrypoint.sh000077500000000000000000000034641526064202200222620ustar00rootroot00000000000000#!/bin/sh set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 1 done until pg_isready -h odyssey -p 6432 -U postgres -d postgres; do echo "Wait for odyssey..." sleep 1 done pgbench -i 'host=odyssey port=6432 user=postgres dbname=postgres' psql 'host=odyssey port=6432 user=postgres dbname=postgres' -c 'select 1' || { echo "error: failed to make query" exit 1 } pgbench 'host=odyssey port=6432 user=postgres dbname=postgres' -T 10 -j 4 -c 16 --no-vacuum --progress 1 || { echo "error: failed to make pgbench query" exit 1 } sleep 1 ./exporter --odyssey.connectionString="host=odyssey port=6432 user=console dbname=console sslmode=disable" & sleep 1 curl tester:9876/metrics -s \ | grep 'odyssey_' \ | grep -v '#' \ | grep -v 'odyssey_exporter_build_info' \ | grep -v 'odyssey_version_info' \ | grep -v 'odyssey_route_bytes_received_total{database="postgres",user="postgres"}' \ | grep -v 'odyssey_route_bytes_sent_total{database="postgres",user="postgres"}' \ | grep -v 'odyssey_database_avg_tx_per_second{database="postgres"}' \ | grep -v 'odyssey_database_avg_query_per_second{database="postgres"}' \ | grep -v 'odyssey_database_avg_query_time_seconds{database="postgres"}' \ | grep -v 'odyssey_database_avg_xact_time_seconds{database="postgres"}' \ | grep -v 'odyssey_database_avg_recv_bytes_per_second{database="postgres"}' \ | grep -v 'odyssey_database_avg_sent_bytes_per_second{database="postgres"}' \ | grep -v 'odyssey_database_avg_wait_time_seconds{database="postgres"}' \ | grep -v 'odyssey_route_tcp_connections_total' \ | grep -v 'odyssey_errors_total{type="OD_ECLIENT_READ"}' > result.out echo EXPECTED START cat expected.out echo EXPECTED END echo echo RESULT START cat result.out echo RESULT END diff expected.out result.out odyssey-1.6.0-rc1/test/prom-exporter/expected.out000066400000000000000000000102031526064202200220270ustar00rootroot00000000000000odyssey_client_pool_active_route{database="console",user="console"} 0 odyssey_client_pool_active_route{database="postgres",user="postgres"} 0 odyssey_client_pool_maxwait_seconds_route{database="console",user="console"} 0 odyssey_client_pool_maxwait_seconds_route{database="postgres",user="postgres"} 0 odyssey_client_pool_queue_route{database="console",user="console"} 0 odyssey_client_pool_queue_route{database="postgres",user="postgres"} 0 odyssey_client_pool_waiting_route{database="console",user="console"} 1 odyssey_client_pool_waiting_route{database="postgres",user="postgres"} 0 odyssey_config_load_failed 0 odyssey_database_avg_query_per_second{database="console"} 0 odyssey_database_avg_query_time_seconds{database="console"} 0 odyssey_database_avg_recv_bytes_per_second{database="console"} 0 odyssey_database_avg_sent_bytes_per_second{database="console"} 0 odyssey_database_avg_tx_per_second{database="console"} 0 odyssey_database_avg_wait_time_seconds{database="console"} 0 odyssey_database_avg_xact_time_seconds{database="console"} 0 odyssey_errors_total{type="OD_EATTACH"} 0 odyssey_errors_total{type="OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH"} 0 odyssey_errors_total{type="OD_EATTACH_TOO_MANY_CONNECTIONS"} 0 odyssey_errors_total{type="OD_ECATCHUP_TIMEOUT"} 0 odyssey_errors_total{type="OD_ECLIENT_COPY_IN_XPROTO"} 0 odyssey_errors_total{type="OD_ECLIENT_KILLED"} 0 odyssey_errors_total{type="OD_ECLIENT_PROTOCOL_ERROR"} 0 odyssey_errors_total{type="OD_ECLIENT_READAHEAD_FULL"} 0 odyssey_errors_total{type="OD_ECLIENT_TIMEOUT"} 0 odyssey_errors_total{type="OD_ECLIENT_WRITE"} 0 odyssey_errors_total{type="OD_EGRACEFUL_SHUTDOWN"} 0 odyssey_errors_total{type="OD_EIDLE_IN_TRANSACTION_TIMEOUT"} 0 odyssey_errors_total{type="OD_EIDLE_TIMEOUT"} 0 odyssey_errors_total{type="OD_EOOM"} 0 odyssey_errors_total{type="OD_ESERVER_CONNECT"} 0 odyssey_errors_total{type="OD_ESERVER_OOM"} 0 odyssey_errors_total{type="OD_ESERVER_READ"} 0 odyssey_errors_total{type="OD_ESERVER_READAHEAD_FULL"} 0 odyssey_errors_total{type="OD_ESERVER_WRITE"} 0 odyssey_errors_total{type="OD_ESYNC_BROKEN"} 0 odyssey_errors_total{type="OD_ROUTER_CLIENT_DISCONNECTED"} 0 odyssey_errors_total{type="OD_ROUTER_ERROR"} 0 odyssey_errors_total{type="OD_ROUTER_ERROR_LIMIT"} 0 odyssey_errors_total{type="OD_ROUTER_ERROR_LIMIT_ROUTE"} 0 odyssey_errors_total{type="OD_ROUTER_ERROR_NOT_FOUND"} 0 odyssey_errors_total{type="OD_ROUTER_ERROR_REPLICATION"} 0 odyssey_errors_total{type="OD_ROUTER_ERROR_TIMEDOUT"} 0 odyssey_exporter_up 1 odyssey_is_paused 0 odyssey_lists_cached_dns_names 0 odyssey_lists_cached_dns_zones 0 odyssey_lists_databases 0 odyssey_lists_free_clients 0 odyssey_lists_free_servers 16 odyssey_lists_in_flight_dns_queries 0 odyssey_lists_login_clients 0 odyssey_lists_pending_dns_queries 0 odyssey_lists_pools 2 odyssey_lists_routing_clients 0 odyssey_lists_used_clients 1 odyssey_lists_used_servers 0 odyssey_lists_users 0 odyssey_route_bytes_received_total{database="console",user="console"} 0 odyssey_route_bytes_sent_total{database="console",user="console"} 0 odyssey_route_pool_mode_info{database="console",mode="session",user="console"} 1 odyssey_route_pool_mode_info{database="postgres",mode="session",user="postgres"} 1 odyssey_server_pool_capacity_configured_route{database="console",user="console"} 0 odyssey_server_pool_capacity_configured_route{database="postgres",user="postgres"} 16 odyssey_server_pool_state_route{database="console",state="active",user="console"} 0 odyssey_server_pool_state_route{database="console",state="idle",user="console"} 0 odyssey_server_pool_state_route{database="console",state="login",user="console"} 0 odyssey_server_pool_state_route{database="console",state="tested",user="console"} 0 odyssey_server_pool_state_route{database="console",state="used",user="console"} 0 odyssey_server_pool_state_route{database="postgres",state="active",user="postgres"} 0 odyssey_server_pool_state_route{database="postgres",state="idle",user="postgres"} 16 odyssey_server_pool_state_route{database="postgres",state="login",user="postgres"} 0 odyssey_server_pool_state_route{database="postgres",state="tested",user="postgres"} 0 odyssey_server_pool_state_route{database="postgres",state="used",user="postgres"} 0 odyssey-1.6.0-rc1/test/prom-exporter/test.conf000066400000000000000000000010761526064202200213330ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_session yes log_query no daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "primary" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "session" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } } odyssey-1.6.0-rc1/test/prometheus-legacy/000077500000000000000000000000001526064202200203135ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/prometheus-legacy/Dockerfile000066400000000000000000000025631526064202200223130ustar00rootroot00000000000000FROM ubuntu:noble RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ cmake \ libmicrohttpd-dev \ postgresql-client-common postgresql-client \ python3 \ wget \ net-tools \ curl \ bison flex \ jq WORKDIR / RUN wget https://github.com/digitalocean/prometheus-client-c/releases/download/v0.1.3/libprom-dev-0.1.3-Linux.deb && \ dpkg -i libprom-dev-0.1.3-Linux.deb RUN wget https://github.com/digitalocean/prometheus-client-c/releases/download/v0.1.3/libpromhttp-dev-0.1.3-Linux.deb && \ dpkg -i libpromhttp-dev-0.1.3-Linux.deb RUN mkdir /odyssey WORKDIR /odyssey COPY . ./ COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop WORKDIR /odyssey ARG odyssey_build_type RUN make clean && make ${odyssey_build_type} COPY ./test/prometheus-legacy/entrypoint.sh /usr/local/bin/entrypoint.sh WORKDIR /odyssey ENTRYPOINT ["entrypoint.sh"]odyssey-1.6.0-rc1/test/prometheus-legacy/config.conf000066400000000000000000000010011526064202200224170ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_file "/var/log/odyssey.log" log_debug no log_config no log_session no log_query no log_stats yes listen { host "127.0.0.1" port 6432 } storage "postgres" { type "remote" host "primary:5432" } database "postgres" { user "postgres" { authentication "none" storage "postgres" pool "session" } } log_general_stats_prom no log_route_stats_prom yes promhttp_server_port 7777 pid_file "/var/run/odyssey.pid" odyssey-1.6.0-rc1/test/prometheus-legacy/docker-compose.yml000066400000000000000000000014721526064202200237540ustar00rootroot00000000000000services: primary: image: postgres:16 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - od_prom_net healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 prometheus: image: prom/prometheus:main volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml networks: - od_prom_net depends_on: - odyssey odyssey: init: true build: dockerfile: ./test/prometheus-legacy/Dockerfile context: ../../ # odyssey root dir args: odyssey_build_type: "${ODYSSEY_PROM_BUILD_TYPE:-build_dbg}" networks: - od_prom_net depends_on: - primary networks: od_prom_net: driver: bridgeodyssey-1.6.0-rc1/test/prometheus-legacy/entrypoint.sh000077500000000000000000000020231526064202200230620ustar00rootroot00000000000000#!/usr/bin/env bash set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 1 done /odyssey/build/sources/odyssey /odyssey/test/prometheus-legacy/config.conf sleep 3 netstat -tulpan | grep ":7777" || { echo "Can't find prom port" ody-stop exit 1 } netstat -tulpan | grep ":6432" || { echo "Can't find odyssey port" ody-stop exit 1 } for (( i=1; i <= 10; i++ )) do psql "host=localhost port=6432 dbname=postgres user=postgres" -A -t -c "select $i" || { echo "error: failed to successfully auth" cat /var/log/odyssey.log ody-stop exit 1 } done curl -v http://localhost:7777/metrics | grep "TYPE" || { echo "Can't find odyssey metrics in prometheus format" ody-stop exit 1 } sleep 1 sleep 1 curl "http://prometheus:9090/api/v1/targets/metadata?job=odyssey" | jq | grep "odyssey" || { echo "Can't find odyssey metrics from prom" ody-stop exit 1 } ody-stop if ps aux | grep -q '[o]dyssey'; then echo "Can't finish odyssey after sigterm" fiodyssey-1.6.0-rc1/test/prometheus-legacy/prometheus.yml000066400000000000000000000002611526064202200232300ustar00rootroot00000000000000global: scrape_interval: 1s scrape_configs: - job_name: 'odyssey' static_configs: - targets: ['odyssey:7777'] fallback_scrape_protocol: "PrometheusText0.0.4" odyssey-1.6.0-rc1/test/proto/000077500000000000000000000000001526064202200160215ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/proto/Dockerfile000066400000000000000000000042021526064202200200110ustar00rootroot00000000000000FROM ubuntu:noble AS ody-build-env ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ cmake \ clang \ libsystemd-dev \ postgresql-client postgresql-client-common postgresql-contrib \ bison flex \ libpthread-stubs0-dev libclang-rt-dev FROM ody-build-env AS ody-builder ARG odyssey_build_type ARG odyssey_cc RUN mkdir build_dir WORKDIR /build_dir COPY . . ENV CC=${odyssey_cc} RUN make clean RUN make ${odyssey_build_type} FROM golang:1.26.0 AS go-builder WORKDIR / ARG spqr_ref=master RUN git clone --depth 1 https://github.com/pg-sharding/spqr.git && \ cd spqr && \ git fetch --depth 1 origin ${spqr_ref} && \ git checkout ${spqr_ref} WORKDIR /spqr RUN go mod tidy ARG test_tag=all RUN go test --tags=${test_tag} -c -o /proto.test ./test/xproto/... FROM ubuntu:noble AS runner ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ postgresql-client postgresql-client-common postgresql-contrib \ python3 \ llvm libpthread-stubs0-dev libclang-rt-dev \ build-essential COPY --from=go-builder /proto.test /usr/bin/proto.test COPY --from=ody-builder /build_dir/build/sources/odyssey /usr/bin/odyssey COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop COPY ./test/proto/odyssey.conf /etc/odyssey.conf COPY ./test/proto/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]odyssey-1.6.0-rc1/test/proto/docker-compose.yml000066400000000000000000000017031526064202200214570ustar00rootroot00000000000000services: primary: image: postgres:16 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - od_proto_test healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 tester: build: context: ../../ # odyssey root dir dockerfile: ./test/proto/Dockerfile args: odyssey_build_type: "${ODYSSEY_BUILD_TYPE:-build_release}" odyssey_cc: "${ODYSSEY_CC:-gcc}" spqr_ref: "${ODYSSEY_PROTO_TEST_SPQR_REF:-master}" test_tag: "${ODYSSEY_PROTO_TEST_TAG:-all}" networks: - od_proto_test environment: ASAN_OPTIONS: "log_path=/asan-output.log fast_unwind_on_malloc=0" # abort_on_error=1 TSAN_OPTIONS: "log_path=/tsan-output.log" depends_on: primary: condition: service_healthy networks: od_proto_test: driver: bridge odyssey-1.6.0-rc1/test/proto/entrypoint.sh000066400000000000000000000010371526064202200205710ustar00rootroot00000000000000#!/bin/sh set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 0.1 done /usr/bin/odyssey /etc/odyssey.conf & ODY_PID=$! until pg_isready -h 127.0.0.1 -p 6432 -U postgres -d postgres; do echo "Wait for odyssey..." sleep 0.1 done do_test() { local user="$1" SPQR_XPROTO_TEST_PURE_PG_ONLY=yes \ POSTGRES_HOST=127.0.0.1 \ POSTGRES_PORT=6432 \ POSTGRES_DB=postgres \ POSTGRES_USER="$user" \ /usr/bin/proto.test \ -test.v } do_test suser do_test tuser ody-stopodyssey-1.6.0-rc1/test/proto/odyssey.conf000066400000000000000000000014511526064202200203700ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_file "/var/odyssey.log" log_session yes log_query no log_stats no daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "primary" port 5432 } database "postgres" { user "suser" { authentication "none" storage "postgres_server" pool "session" pool_size 10 storage_user "postgres" client_fwd_error yes } user "tuser" { authentication "none" storage "postgres_server" pool "transaction" pool_size 10 storage_user "postgres" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/quickstart/000077500000000000000000000000001526064202200170505ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/quickstart/Dockerfile000066400000000000000000000004331526064202200210420ustar00rootroot00000000000000FROM alpine:latest RUN apk add --no-cache \ postgresql-client \ python3 COPY ./test/quickstart/entrytest.sh /usr/local/bin/entrytest.sh COPY ./test/functional/bin/ody-stop /usr/local/bin/ody-stop RUN chmod +x /usr/local/bin/entrytest.sh ENTRYPOINT ["entrytest.sh"]odyssey-1.6.0-rc1/test/quickstart/docker-compose.yml000066400000000000000000000013121526064202200225020ustar00rootroot00000000000000services: primary: image: postgres:16 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres networks: - od_alpine_build healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey volumes: - ./test.conf:/etc/odyssey/odyssey.conf networks: - od_alpine_build depends_on: - primary tester: build: context: ../../ # odyssey root dir dockerfile: ./test/quickstart/Dockerfile networks: - od_alpine_build depends_on: - odyssey networks: od_alpine_build: driver: bridge odyssey-1.6.0-rc1/test/quickstart/entrytest.sh000066400000000000000000000011671526064202200214520ustar00rootroot00000000000000#!/bin/sh set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 0.1 done until pg_isready -h odyssey -p 6432 -U postgres -d postgres; do echo "Wait for Odyssey..." sleep 0.1 done pgbench -i 'host=odyssey port=6432 user=postgres dbname=postgres' psql 'host=odyssey port=6432 user=postgres dbname=postgres' -c 'select 1' || { echo "error: failed to make query" ody-stop exit 1 } pgbench 'host=odyssey port=6432 user=postgres dbname=postgres' -T 20 -j 4 -c 16 --no-vacuum --progress 1 || { echo "error: failed to run pgbench" ody-stop exit 1 } odyssey-1.6.0-rc1/test/quickstart/test.conf000066400000000000000000000010751526064202200207010ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_session yes log_query no daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "primary" port 5432 } database default { user default { authentication "none" storage "postgres_server" pool "session" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/replication/000077500000000000000000000000001526064202200171675ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/replication/Dockerfile000066400000000000000000000005211526064202200211570ustar00rootroot00000000000000FROM ubuntu:resolute RUN apt-get update -y && apt-get install -y postgresql-client postgresql-client-common postgresql-contrib procps COPY ./test/replication/entrytest.sh /usr/local/bin/entrytest.sh COPY ./test/replication/logical_expected.out /logical_expected.out RUN chmod +x /usr/local/bin/entrytest.sh ENTRYPOINT ["entrytest.sh"]odyssey-1.6.0-rc1/test/replication/conf/000077500000000000000000000000001526064202200201145ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/replication/conf/common.conf000066400000000000000000000005111526064202200222500ustar00rootroot00000000000000unix_socket_dir "/tmp" unix_socket_mode "0644" log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout yes log_session yes log_query no log_stats no daemonize no locks_dir "/tmp/odyssey" listen { host "*" port 6432 } storage "local" { type "local" } storage "postgres_server" { type "remote" host "primary" port 5432 }odyssey-1.6.0-rc1/test/replication/conf/odyssey.conf000066400000000000000000000004611526064202200224630ustar00rootroot00000000000000include "/etc/odyssey/common.conf" database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 10 client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/replication/conf/odyssey_blocked.conf000066400000000000000000000006711526064202200241510ustar00rootroot00000000000000include "/etc/odyssey/common.conf" database "db1" { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 10 client_fwd_error yes } } database default { user default { authentication "block" storage "postgres_server" pool "transaction" client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/replication/conf/odyssey_replication.conf000066400000000000000000000007161526064202200250570ustar00rootroot00000000000000include "/etc/odyssey/common.conf" database default { user default { authentication "none" storage "postgres_server" pool "transaction" pool_size 10 client_fwd_error yes } } database "replication" { user "repl_user" { authentication "none" storage "postgres_server" pool "session" pool_size 5 client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.6.0-rc1/test/replication/docker-compose.yml000066400000000000000000000021751526064202200226310ustar00rootroot00000000000000services: primary: image: postgres:16 environment: - POSTGRES_USER=postgres - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_DB=postgres command: - postgres - -c - wal_level=logical - -c - max_wal_senders=10 - -c - max_replication_slots=10 - -c - hot_standby=on - -c - listen_addresses=* - -c - hba_file=/etc/postgresql/pg_hba.conf volumes: - ./pg_hba.conf:/etc/postgresql/pg_hba.conf:ro networks: - od_replication_test healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 odyssey: image: odyssey volumes: - ./conf:/etc/odyssey networks: - od_replication_test depends_on: primary: condition: service_healthy tester: build: context: ../../ # odyssey root dir dockerfile: ./test/replication/Dockerfile pid: "service:odyssey" volumes: - ./conf:/conf networks: - od_replication_test depends_on: - odyssey networks: od_replication_test: driver: bridge odyssey-1.6.0-rc1/test/replication/entrytest.sh000066400000000000000000000150031526064202200215630ustar00rootroot00000000000000#!/bin/sh set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 0.1 done CONN='-h odyssey -p 6432 -U postgres -d postgres' REPLSLOTNAME='test_slot' until pg_isready $CONN; do echo "Wait for odyssey..." sleep 0.1 done lsn_ge() { local left="$1" local right="$2" result="$(psql $CONN -Atqc "select pg_lsn('$left') >= pg_lsn('$right')")" [ "$result" = "t" ] } get_confirmed_flush_lsn() { local slotname="$1" psql $CONN -Atqc " select coalesce(confirmed_flush_lsn::text, '') from pg_replication_slots where slot_name = '$slotname' " } wait_for_confirmed_flush_lsn_ge() { local target_lsn="$1" local slotname="$2" for _ in $(seq 1 30); do current_lsn="$(get_confirmed_flush_lsn $slotname)" if [ -n "$current_lsn" ] && lsn_ge "$current_lsn" "$target_lsn"; then echo "confirmed_flush_lsn reached target: $current_lsn >= $target_lsn" return 0 fi sleep 1 done echo "confirmed_flush_lsn did not reach target $target_lsn" >&2 current_lsn="$(get_confirmed_flush_lsn $slotname)" echo "current confirmed_flush_lsn: $current_lsn" >&2 exit 1 } do_logical_repl_test() { psql $CONN -c "select pg_drop_replication_slot('$REPLSLOTNAME')" || true psql $CONN -c 'drop table if exists z' psql $CONN -c 'create table z(x int, y int, z int)' pg_recvlogical $CONN --slot $REPLSLOTNAME --create-slot -P test_decoding psql $CONN -c 'insert into z values(1, 2, 3)' psql $CONN -c 'insert into z values(4, 5, 6)' psql $CONN -c 'insert into z values(7, 8, 9)' echo "=== START 1 ===" >/logical_actual.out pg_recvlogical $CONN --slot $REPLSLOTNAME --start -f - -o include-xids=0 >>/logical_actual.out 2>&1 & pid=$! psql $CONN -c 'insert into z values(10, 11, 12)' psql $CONN -c 'insert into z values(13, 14, 15)' psql $CONN -c 'insert into z values(16, 17, 18)' target_lsn="$(psql $CONN -Atqc "select pg_current_wal_lsn()")" wait_for_confirmed_flush_lsn_ge $target_lsn $REPLSLOTNAME kill -INT $pid wait $pid || { echo 'recvlogical failed' exit 1 } unset pid psql $CONN -c 'insert into z values(19, 20, 21)' psql $CONN -c 'insert into z values(22, 23, 24)' psql $CONN -c 'insert into z values(25, 26, 27)' echo "=== START 2 ===" >>/logical_actual.out pg_recvlogical $CONN --slot $REPLSLOTNAME --start -f - -o include-xids=0 >>/logical_actual.out 2>&1 & pid=$! psql $CONN -c 'insert into z values(28, 29, 30)' psql $CONN -c 'insert into z values(31, 32, 33)' psql $CONN -c 'insert into z values(34, 35, 36)' target_lsn="$(psql $CONN -Atqc "select pg_current_wal_lsn()")" wait_for_confirmed_flush_lsn_ge $target_lsn $REPLSLOTNAME kill -INT $pid wait $pid || { echo 'recvlogical failed' exit 1 } unset pid diff /logical_actual.out /logical_expected.out || { cat /logical_actual.out exit 1 } } do_physical_repl_test() { psql $CONN -c "select pg_drop_replication_slot('$REPLSLOTNAME')" || true local backup_dir='/tmp/backup' rm -rf $backup_dir pg_basebackup -h odyssey -p 6432 -U postgres -D "$backup_dir" -X stream --checkpoint=fast -Fp -R -C -S "$REPLSLOTNAME" -v || { echo "pg_basebackup failed" exit 1 } test -f "$backup_dir/PG_VERSION" test -d "$backup_dir/base" test -d "$backup_dir/global" test -f "$backup_dir/standby.signal" test -f "$backup_dir/postgresql.auto.conf" grep -q "primary_conninfo" "$backup_dir/postgresql.auto.conf" psql $CONN -Atqc "select slot_name from pg_replication_slots where slot_name = '$REPLSLOTNAME' and slot_type = 'physical'" | grep -q "^$REPLSLOTNAME$" } reload() { psql -h odyssey -p 6432 -U console -d console -c 'reload' || exit 1 psql -h odyssey -p 6432 -U console -d console -c 'gc' || exit 1 } use_config() { local name="$1" echo "Doing config $name.." diff "/conf/$name" /conf/odyssey.conf || true cp /conf/odyssey.conf /conf/odyssey.conf.bak cp "/conf/$name" /conf/odyssey.conf reload } restore_config() { cp /conf/odyssey.conf.bak /conf/odyssey.conf rm /conf/odyssey.conf.bak reload } pool_mode() { psql -h odyssey -p 6432 -U console -d console -Atqc 'show pools' | grep '^replication|repl_user|' | grep -o '[a-z]*$' } do_replication_db_test() { # a database named "replication" must not be mistaken for the replication parameter psql -h primary -p 5432 -U postgres -d postgres -c 'drop database if exists replication' || true psql -h primary -p 5432 -U postgres -d postgres -c 'create database replication' psql -h primary -p 5432 -U postgres -d postgres -c 'drop role if exists repl_user' || true psql -h primary -p 5432 -U postgres -d postgres -c 'create role repl_user login' [ "$(psql -h odyssey -p 6432 -U repl_user -d replication -Atqc 'select current_database()')" = "replication" ] psql -h odyssey -p 6432 -U repl_user -d replication -Atqc 'select 1' | grep -qx '1' # base config routes it via default rule [ "$(pool_mode)" = "transaction" ] || { psql -h odyssey -p 6432 -U console -d console -c 'show pools' psql -h odyssey -p 6432 -U console -d console -c 'show rules' exit 1 } use_config odyssey_replication.conf psql -h odyssey -p 6432 -U repl_user -d replication -Atqc 'select 1' | grep -qx '1' # dedicated route applied after reload [ "$(pool_mode)" = "session" ] || { psql -h odyssey -p 6432 -U console -d console -c 'show pools' psql -h odyssey -p 6432 -U console -d console -c 'show rules' exit 1 } restore_config } do_replication_blocked_test() { # only db1 allowed; everything else (incl. replication) is blocked psql -h primary -p 5432 -U postgres -d postgres -c 'drop database if exists db1' || true psql -h primary -p 5432 -U postgres -d postgres -c 'create database db1' use_config odyssey_blocked.conf psql -h odyssey -p 6432 -U postgres -d db1 -Atqc 'select 1' | grep -qx '1' if psql -h odyssey -p 6432 -U postgres -d postgres -Atqc 'select 1' 2>/dev/null; then echo "non-db1 database should be blocked" exit 1 fi if psql "host=odyssey port=6432 user=postgres dbname=replication replication=true" 2>/dev/null; then echo "replication connection should be blocked" exit 1 fi restore_config } do_logical_repl_test do_physical_repl_test do_replication_db_test do_replication_blocked_test odyssey-1.6.0-rc1/test/replication/logical_expected.out000066400000000000000000000017131526064202200232150ustar00rootroot00000000000000=== START 1 === BEGIN table public.z: INSERT: x[integer]:1 y[integer]:2 z[integer]:3 COMMIT BEGIN table public.z: INSERT: x[integer]:4 y[integer]:5 z[integer]:6 COMMIT BEGIN table public.z: INSERT: x[integer]:7 y[integer]:8 z[integer]:9 COMMIT BEGIN table public.z: INSERT: x[integer]:10 y[integer]:11 z[integer]:12 COMMIT BEGIN table public.z: INSERT: x[integer]:13 y[integer]:14 z[integer]:15 COMMIT BEGIN table public.z: INSERT: x[integer]:16 y[integer]:17 z[integer]:18 COMMIT === START 2 === BEGIN table public.z: INSERT: x[integer]:19 y[integer]:20 z[integer]:21 COMMIT BEGIN table public.z: INSERT: x[integer]:22 y[integer]:23 z[integer]:24 COMMIT BEGIN table public.z: INSERT: x[integer]:25 y[integer]:26 z[integer]:27 COMMIT BEGIN table public.z: INSERT: x[integer]:28 y[integer]:29 z[integer]:30 COMMIT BEGIN table public.z: INSERT: x[integer]:31 y[integer]:32 z[integer]:33 COMMIT BEGIN table public.z: INSERT: x[integer]:34 y[integer]:35 z[integer]:36 COMMIT odyssey-1.6.0-rc1/test/replication/pg_hba.conf000066400000000000000000000005361526064202200212620ustar00rootroot00000000000000local all all trust host all all 0.0.0.0/0 trust host replication all 0.0.0.0/0 trust host all all ::0/0 trust host replication all ::0/0 trust odyssey-1.6.0-rc1/test/scram-passthrough/000077500000000000000000000000001526064202200203305ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/scram-passthrough/.dockerignore000066400000000000000000000000121526064202200227750ustar00rootroot00000000000000Dockerfileodyssey-1.6.0-rc1/test/scram-passthrough/Dockerfile000066400000000000000000000031131526064202200223200ustar00rootroot00000000000000FROM ubuntu:noble AS common-builder ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ openssl \ libpam0g-dev \ build-essential \ cmake \ bison flex \ pkg-config \ libreadline-dev \ clang \ libsystemd-dev \ libpthread-stubs0-dev libclang-rt-dev \ python3 FROM common-builder AS pg-build RUN git clone --depth 1 --branch REL_18_STABLE https://github.com/postgres/postgres.git RUN cd postgres && ./configure --prefix /usr --without-icu --without-zlib && make -j install >/dev/null FROM pg-build AS odyssey-build ARG build_type=build_dbg ARG odyssey_cc=gcc RUN mkdir /build_dir WORKDIR /build_dir COPY . . RUN CC=${odyssey_cc} make clean && make ${build_type} RUN cp /build_dir/build/sources/odyssey /usr/bin/odyssey COPY ./sources/machinarium/gdb/machinarium-gdb.py /gdb.py COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop FROM odyssey-build AS runner RUN useradd -ms /bin/bash postgres RUN mkdir regress WORKDIR /home/postgres COPY ./test/scram-passthrough/entrypoint.sh /home/postgres/entrypoint.sh COPY ./test/scram-passthrough/odyssey.conf /home/postgres/odyssey.conf RUN chown postgres /home/postgres/entrypoint.sh USER postgres ENTRYPOINT ["/home/postgres/entrypoint.sh"] odyssey-1.6.0-rc1/test/scram-passthrough/entrypoint.sh000077500000000000000000000032161526064202200231040ustar00rootroot00000000000000#!/usr/bin/env bash set -xu PGDIR=/home/postgres/pgdir pg_ctl stop -D ${PGDIR} -l ${PGDIR}/log.txt || true rm -rf ${PGDIR} || true initdb -D ${PGDIR} cat >> "${PGDIR}/postgresql.conf" < "${PGDIR}/pg_hba.conf" </dev/null 2>&1; do sleep 0.2 done psql -h localhost -U postgres -d postgres -c 'create database testdb' || exit 1 psql -h localhost -U postgres -d postgres -c "create role test_scram with password 'password' login" || exit 1 PGPASSWORD=password psql -h localhost -U test_scram -d testdb -c 'select 42' || exit 1 PGPASSWORD=not-password psql -h localhost -U test_scram -d testdb -c 'select 42' && { echo "successfully auth with incorrect password" exit 1 } odyssey /home/postgres/odyssey.conf || exit 1 until pg_isready -h localhost -p 6432 >/dev/null 2>&1; do sleep 0.2 done PGPASSWORD=password psql -h localhost -p 6432 -U test_scram -d testdb -c 'select 42' || { cat /home/postgres/odyssey.log exit 1 } PGPASSWORD=not-password psql -h localhost -p 6432 -U test_scram -d testdb -c 'select 42' && { echo "successfully auth with incorrect password" cat /home/postgres/odyssey.log exit 1 } ody-stop exit 0 odyssey-1.6.0-rc1/test/scram-passthrough/odyssey.conf000066400000000000000000000016661526064202200227070ustar00rootroot00000000000000pid_file "/tmp/odyssey.pid" daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" log_debug yes log_config yes log_session yes log_query no log_stats no log_file "/home/postgres/odyssey.log" stats_interval 60 workers 1 readahead 4096 cache_coroutine 0 listen { host "*" port 6432 } storage "postgres_server" { type "remote" host "localhost:5432" } database "postgres" { user "postgres" { authentication "none" pool_routing "internal" storage "postgres_server" pool "session" } } database default { user default { authentication "scram-sha-256" auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" auth_query_db "postgres" auth_query_user "postgres" storage "postgres_server" pool "transaction" pool_size 32 } } locks_dir "/tmp/odyssey" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yesodyssey-1.6.0-rc1/test/stress/000077500000000000000000000000001526064202200162015ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/stress/Dockerfile000066400000000000000000000057651526064202200202100ustar00rootroot00000000000000FROM golang:1.25 AS tester-build WORKDIR /app COPY ./test/stress/go.mod ./go.mod COPY ./test/stress/go.sum ./go.sum COPY ./test/stress/pkg ./pkg RUN go mod tidy && cd pkg && go build -o stester FROM ubuntu:noble AS odyssey-build-env ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Europe/Moskow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ openssl \ libpam0g-dev \ build-essential \ bison flex \ cmake FROM odyssey-build-env AS odyssey-build ARG odyssey_build_type RUN mkdir /build_dir WORKDIR /build_dir COPY . . RUN make clean && make ${odyssey_build_type} RUN cp /build_dir/build/sources/odyssey /odyssey COPY ./test/stress/odyssey.conf /odyssey.conf FROM ubuntu:noble AS test-runner-env ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ postgresql-client postgresql-client-common postgresql-contrib \ openssh-server \ vim \ gdb \ build-essential \ strace ltrace \ lsof \ iproute2 \ python3 # Taken from - https://docs.docker.com/engine/examples/running_ssh_service/#environment-variables RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd RUN echo 'PermitRootLogin yes' > /etc/ssh/sshd_config.d/10.myconf.conf # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE="in users profile" RUN echo "export VISIBLE=now" >> /etc/profile # 22 for ssh server. 7777 for gdb server. EXPOSE 22 7777 RUN useradd -ms /bin/bash debugger RUN echo 'debugger:pwd' | chpasswd WORKDIR /stress-test COPY ./sources/machinarium/gdb/machinarium-gdb.py /gdb.py COPY ./test/functional/bin/ody-stop /usr/bin/ody-stop COPY --from=odyssey-build /odyssey /odyssey COPY --from=odyssey-build /odyssey.conf /odyssey.conf COPY --from=odyssey-build /build_dir /build_dir COPY --from=tester-build /app/pkg/stester /stester COPY ./test/stress/entrypoint.sh ./ FROM test-runner-env AS stress-entrypoint WORKDIR /stress-test ENTRYPOINT ["/stress-test/entrypoint.sh"] FROM test-runner-env AS dev-env WORKDIR /stress-test ENTRYPOINT ["/usr/sbin/sshd", "-D"]odyssey-1.6.0-rc1/test/stress/docker-compose.yml000066400000000000000000000027461526064202200216470ustar00rootroot00000000000000services: primary: image: postgres:17 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres volumes: - ./primary-init:/docker-entrypoint-initdb.d command: | postgres -c wal_level=replica -c hot_standby=on -c max_wal_senders=10 -c max_replication_slots=10 -c hot_standby_feedback=on networks: - od_stress_net healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 replica: image: postgres:17 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres volumes: - ./replica-init:/docker-entrypoint-initdb.d networks: - od_stress_net depends_on: - primary healthcheck: test: 'pg_isready -U postgres --dbname=postgres' interval: 1s timeout: 1s retries: 3 runner: init: true privileged: true build: dockerfile: ./test/stress/Dockerfile context: ../../ # odyssey root dir target: "${ODYSSEY_STRESS_TEST_TARGET:-stress-entrypoint}" args: odyssey_build_type: "${ODYSSEY_STRESS_BUILD_TYPE:-build_release}" environment: ASAN_OPTIONS: "log_path=/asan-output.log fast_unwind_on_malloc=0" TSAN_OPTIONS: "log_path=/tsan-output.log" networks: - od_stress_net depends_on: - replica networks: od_stress_net: driver: bridge odyssey-1.6.0-rc1/test/stress/entrypoint.sh000077500000000000000000000040121526064202200207500ustar00rootroot00000000000000#!/usr/bin/env bash # TODO: create fuzzing tests here set -eux # Bound loopback segments while preserving slow reads of large rows. ip link set dev lo mtu 1500 ip -o link show dev lo until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 1 done until pg_isready -h replica -p 5432 -U postgres -d postgres; do echo "Wait for replica..." sleep 1 done /odyssey /odyssey.conf sleep 1 # Under sanitizers (ASAN/TSAN) Odyssey runs 2-3x slower, so we relax # latency limits to avoid flaky failures on CI. if [ -n "${ASAN_OPTIONS:-}" ] || [ -n "${TSAN_OPTIONS:-}" ]; then SMALL_MAX_LATENCY=3s PREPARED_MAX_LATENCY=5s TX_MAX_LATENCY=5s SMALL_TIMEOUT=5s PREPARED_TIMEOUT=5s TX_TIMEOUT=5s ELEPHANT_TIMEOUT=90s ELEPHANT_MAX_DURATION=70s else SMALL_MAX_LATENCY=1s PREPARED_MAX_LATENCY=2s TX_MAX_LATENCY=2s SMALL_TIMEOUT=2s PREPARED_TIMEOUT=2s TX_TIMEOUT=2s ELEPHANT_TIMEOUT=60s ELEPHANT_MAX_DURATION=50s fi /stester -dsn 'postgres://tuser:postgres@localhost:6432/postgres?sslmode=disable' \ -duration 10m \ -startup-stagger-max 30s \ -fail-fast=true \ -connect-timeout 1s \ -reconnect-prob 0.5 \ -small-clients 100 \ -small-think-min 20ms \ -small-think-max 500ms \ -small-timeout "$SMALL_TIMEOUT" \ -small-max-latency "$SMALL_MAX_LATENCY" \ -prepared-clients 50 \ -prepared-rows 10 \ -prepared-think-min 20ms \ -prepared-think-max 500ms \ -prepared-timeout "$PREPARED_TIMEOUT" \ -prepared-max-latency "$PREPARED_MAX_LATENCY" \ -tx-clients 4 \ -tx-sleep 1s \ -tx-think-min 100ms \ -tx-think-max 200ms \ -tx-timeout "$TX_TIMEOUT" \ -tx-max-latency "$TX_MAX_LATENCY" \ -elephant-clients 4 \ -elephant-chunk-bytes 65536 \ -elephant-chunks 20 \ -elephant-row-delay 1s \ -elephant-drop-prob 0.15 \ -elephant-timeout "$ELEPHANT_TIMEOUT" \ -elephant-max-factor 1.2 \ -elephant-max-duration "$ELEPHANT_MAX_DURATION" || { sleep 1 cat /odyssey.log for i in /asan-output*; do cat $i done exit 1 } ody-stop odyssey-1.6.0-rc1/test/stress/go.mod000066400000000000000000000003741526064202200173130ustar00rootroot00000000000000module odyssey_stress go 1.25.0 require github.com/jackc/pgx/v5 v5.9.2 require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect golang.org/x/text v0.29.0 // indirect ) odyssey-1.6.0-rc1/test/stress/go.sum000066400000000000000000000043731526064202200173430ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= odyssey-1.6.0-rc1/test/stress/odyssey.conf000066400000000000000000000015631526064202200205540ustar00rootroot00000000000000daemonize yes log_format "%p %t %l [%i %s] (%c) %m\n" log_to_stdout no log_debug no log_config yes log_session no log_query no log_stats yes workers 2 bindwith_reuseport yes log_file "/odyssey.log" stats_interval 60 listen { host "0.0.0.0" port 6432 } storage "local" { type "local" } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } user default { authentication "block" pool "session" storage "local" } } storage "postgres" { type "remote" host "primary:5432" } database "postgres" { user "tuser" { authentication "md5" password "postgres" storage "postgres" storage_password "postgres" pool "transaction" pool_size 50 target_session_attrs "read-write" } }odyssey-1.6.0-rc1/test/stress/pkg/000077500000000000000000000000001526064202200167625ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/stress/pkg/main.go000066400000000000000000000742611526064202200202470ustar00rootroot00000000000000package main import ( "context" "crypto/md5" "errors" "flag" "fmt" "log" "math/rand" "os" "os/signal" "strconv" "strings" "sync" "sync/atomic" "syscall" "time" "github.com/jackc/pgx/v5" ) type Config struct { DSN string Duration time.Duration ConnectTimeout time.Duration FailFast bool ReconnectProb float64 StartupStaggerMax time.Duration SmallClients int SmallThinkMin time.Duration SmallThinkMax time.Duration SmallQueryTimeout time.Duration SmallMaxLatency time.Duration PreparedClients int PreparedRows int PreparedThinkMin time.Duration PreparedThinkMax time.Duration PreparedQueryTimeout time.Duration PreparedMaxLatency time.Duration TxClients int TxSleep time.Duration TxThinkMin time.Duration TxThinkMax time.Duration TxQueryTimeout time.Duration TxMaxLatency time.Duration ElephantClients int ElephantChunkBytes int ElephantChunks int ElephantRowDelay time.Duration ElephantDropProb float64 ElephantQueryTimeout time.Duration ElephantMaxFactor float64 ElephantMaxDuration time.Duration } type OpKind int const ( KindSmall OpKind = iota KindPrepared KindTx KindElephant ) func (k OpKind) String() string { switch k { case KindSmall: return "small" case KindPrepared: return "prepared" case KindTx: return "tx" case KindElephant: return "elephant" default: return "unknown" } } type Event struct { Kind OpKind WorkerID int Success bool Timeout bool Dropped bool Latency time.Duration Limit time.Duration OverLimit bool Err string } type Metrics struct { Ok int64 Err int64 Timeout int64 Dropped int64 Slow int64 MaxLatency time.Duration } func (m *Metrics) Record(ev Event) { if ev.Success { m.Ok++ } else if ev.Dropped { m.Dropped++ } else { m.Err++ } if ev.Timeout { m.Timeout++ } if ev.OverLimit { m.Slow++ } if ev.Latency > m.MaxLatency { m.MaxLatency = ev.Latency } } func (m *Metrics) Count() int64 { return m.Ok + m.Err + m.Dropped } type Gauges struct { SmallActive atomic.Int64 PreparedActive atomic.Int64 TxActive atomic.Int64 ElephantActive atomic.Int64 ElephantBytes atomic.Int64 ElephantRows atomic.Int64 } type Collector struct { start time.Time cfg Config gauges *Gauges cancel context.CancelFunc window map[OpKind]*Metrics total map[OpKind]*Metrics lastElephantBytes int64 lastElephantRows int64 failed bool failReason string } func newCollector(cfg Config, gauges *Gauges, cancel context.CancelFunc) *Collector { return &Collector{ start: time.Now(), cfg: cfg, gauges: gauges, cancel: cancel, window: map[OpKind]*Metrics{ KindSmall: {}, KindPrepared: {}, KindTx: {}, KindElephant: {}, }, total: map[OpKind]*Metrics{ KindSmall: {}, KindPrepared: {}, KindTx: {}, KindElephant: {}, }, } } func (c *Collector) record(ev Event) { c.window[ev.Kind].Record(ev) c.total[ev.Kind].Record(ev) } func (c *Collector) resetWindow() { c.window[KindSmall] = &Metrics{} c.window[KindPrepared] = &Metrics{} c.window[KindTx] = &Metrics{} c.window[KindElephant] = &Metrics{} } func (c *Collector) markFailed(reason string) { if c.failed { return } c.failed = true c.failReason = reason log.Printf("FAIL: %s", reason) if c.cfg.FailFast { c.cancel() } } func (c *Collector) handleEvent(ev Event) { c.record(ev) if ev.OverLimit { c.markFailed(fmt.Sprintf( "%s worker=%d exceeded latency limit: got=%s limit=%s", ev.Kind, ev.WorkerID, ev.Latency, ev.Limit, )) } if !ev.Success && !ev.Dropped { c.markFailed(fmt.Sprintf( "%s worker=%d failed: latency=%s timeout=%v err=%q", ev.Kind, ev.WorkerID, ev.Latency, ev.Timeout, ev.Err, )) } } func (c *Collector) run(events <-chan Event, done chan<- struct{}) { defer close(done) ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case ev, ok := <-events: if !ok { c.printFinal() return } c.handleEvent(ev) case <-ticker.C: c.printTick() c.resetWindow() } } } func (c *Collector) printTick() { elapsed := time.Since(c.start).Truncate(time.Second) sb := &strings.Builder{} fmt.Fprintf(sb, "[%8s] ", elapsed) sm := c.window[KindSmall] fmt.Fprintf( sb, "small act=%d qps=%d ok=%d err=%d to=%d slow=%d max=%s limit=%s\n", c.gauges.SmallActive.Load(), sm.Count(), sm.Ok, sm.Err, sm.Timeout, sm.Slow, durStr(sm.MaxLatency), c.cfg.SmallMaxLatency, ) pr := c.window[KindPrepared] fmt.Fprintf( sb, " prep act=%d qps=%d ok=%d err=%d to=%d slow=%d max=%s limit=%s\n", c.gauges.PreparedActive.Load(), pr.Count(), pr.Ok, pr.Err, pr.Timeout, pr.Slow, durStr(pr.MaxLatency), c.cfg.PreparedMaxLatency, ) tx := c.window[KindTx] fmt.Fprintf( sb, " tx act=%d qps=%d ok=%d err=%d to=%d slow=%d max=%s limit=%s\n", c.gauges.TxActive.Load(), tx.Count(), tx.Ok, tx.Err, tx.Timeout, tx.Slow, durStr(tx.MaxLatency), c.cfg.TxMaxLatency, ) el := c.window[KindElephant] bytesNow := c.gauges.ElephantBytes.Load() rowsNow := c.gauges.ElephantRows.Load() bytesDelta := bytesNow - c.lastElephantBytes rowsDelta := rowsNow - c.lastElephantRows c.lastElephantBytes = bytesNow c.lastElephantRows = rowsNow fmt.Fprintf( sb, " eleph act=%d read=%s/s rows=%d/s streams=%d ok=%d err=%d drop=%d slow=%d max=%s limit=%s\n", c.gauges.ElephantActive.Load(), formatBytes(bytesDelta), rowsDelta, el.Count(), el.Ok, el.Err, el.Dropped, el.Slow, durStr(el.MaxLatency), elephantLimit(c.cfg), ) fmt.Print(sb.String()) } func (c *Collector) printFinal() { fmt.Println("\n==== FINAL SUMMARY ====") fmt.Printf("runtime: %s\n", time.Since(c.start).Truncate(time.Second)) fmt.Printf("failed : %v\n", c.failed) if c.failReason != "" { fmt.Printf("reason : %s\n", c.failReason) } fmt.Println() printFinalKind("small", c.total[KindSmall], c.cfg.SmallMaxLatency) printFinalKind("prepared", c.total[KindPrepared], c.cfg.PreparedMaxLatency) printFinalKind("tx", c.total[KindTx], c.cfg.TxMaxLatency) printFinalKind("elephant", c.total[KindElephant], elephantLimit(c.cfg)) fmt.Println() fmt.Printf("elephant total bytes read: %s\n", formatBytes(c.gauges.ElephantBytes.Load())) fmt.Printf("elephant total rows read : %d\n", c.gauges.ElephantRows.Load()) } func printFinalKind(name string, m *Metrics, limit time.Duration) { fmt.Printf( "%-8s total=%d ok=%d err=%d timeout=%d dropped=%d slow=%d max=%s limit=%s\n", name, m.Count(), m.Ok, m.Err, m.Timeout, m.Dropped, m.Slow, durStr(m.MaxLatency), limit, ) } func elephantLimit(cfg Config) time.Duration { if cfg.ElephantMaxDuration > 0 { return cfg.ElephantMaxDuration } return time.Duration(float64(cfg.ElephantChunks) * float64(cfg.ElephantRowDelay) * cfg.ElephantMaxFactor) } func durStr(d time.Duration) string { if d <= 0 { return "-" } return d.String() } func formatBytes(n int64) string { const ( KB = 1024 MB = 1024 * KB GB = 1024 * MB ) switch { case n >= GB: return fmt.Sprintf("%.2fGB", float64(n)/float64(GB)) case n >= MB: return fmt.Sprintf("%.2fMB", float64(n)/float64(MB)) case n >= KB: return fmt.Sprintf("%.2fKB", float64(n)/float64(KB)) default: return fmt.Sprintf("%dB", n) } } func connectSimpleOnce(ctx context.Context, dsn string, connectTimeout time.Duration, appName string) (*pgx.Conn, error) { cfg, err := pgx.ParseConfig(dsn) if err != nil { return nil, err } cfg.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol cfg.ConnectTimeout = connectTimeout if cfg.RuntimeParams == nil { cfg.RuntimeParams = map[string]string{} } cfg.RuntimeParams["application_name"] = appName cctx, cancel := context.WithTimeout(ctx, connectTimeout) defer cancel() return pgx.ConnectConfig(cctx, cfg) } func connectPreparedOnce(ctx context.Context, dsn string, connectTimeout time.Duration, appName string) (*pgx.Conn, error) { cfg, err := pgx.ParseConfig(dsn) if err != nil { return nil, err } cfg.ConnectTimeout = connectTimeout if cfg.RuntimeParams == nil { cfg.RuntimeParams = map[string]string{} } cfg.RuntimeParams["application_name"] = appName cctx, cancel := context.WithTimeout(ctx, connectTimeout) defer cancel() return pgx.ConnectConfig(cctx, cfg) } func closeConn(conn *pgx.Conn) { if conn == nil { return } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) _ = conn.Close(ctx) cancel() } func sleepCtx(ctx context.Context, d time.Duration) bool { if d <= 0 { return ctx.Err() == nil } t := time.NewTimer(d) defer t.Stop() select { case <-ctx.Done(): return false case <-t.C: return true } } func sleepCtxOrStop(ctx context.Context, stopCh <-chan struct{}, d time.Duration) bool { if d <= 0 { select { case <-ctx.Done(): return false case <-stopCh: return false default: return true } } t := time.NewTimer(d) defer t.Stop() select { case <-ctx.Done(): return false case <-stopCh: return false case <-t.C: return true } } func randDuration(r *rand.Rand, min, max time.Duration) time.Duration { if max <= min { return min } delta := max - min return min + time.Duration(r.Int63n(int64(delta)+1)) } func shouldReconnect(r *rand.Rand, p float64) bool { return p > 0 && r.Float64() < p } func shortErr(err error) string { if err == nil { return "" } s := err.Error() if len(s) > 220 { return s[:220] + "..." } return s } func sendEvent(ctx context.Context, events chan<- Event, ev Event) bool { select { case events <- ev: return true case <-ctx.Done(): return false } } func canStartNewOp(ctx context.Context, stopCh <-chan struct{}) bool { select { case <-ctx.Done(): return false case <-stopCh: return false default: return true } } func maybeInitialStagger(ctx context.Context, stopCh <-chan struct{}, r *rand.Rand, max time.Duration) bool { if max <= 0 { return canStartNewOp(ctx, stopCh) } return sleepCtxOrStop(ctx, stopCh, randDuration(r, 0, max)) } func expectedPreparedValue(seed, rowNum int) string { sum := md5.Sum([]byte(strconv.Itoa(seed + rowNum))) hex := fmt.Sprintf("%x", sum) return hex[:10] } func validatePreparedRow(expectedRow, gotRow int, seed int, got string) error { if gotRow != expectedRow { return fmt.Errorf("unexpected row num=%d expected=%d", gotRow, expectedRow) } want := expectedPreparedValue(seed, expectedRow) if got != want { return fmt.Errorf("unexpected row value=%q expected=%q row=%d seed=%d", got, want, expectedRow, seed) } return nil } func validateElephantRow(expectedRow, gotRow int, payload []byte, chunkBytes int) error { if gotRow != expectedRow { return fmt.Errorf("unexpected row num=%d expected=%d", gotRow, expectedRow) } if len(payload) != chunkBytes { return fmt.Errorf("unexpected payload len=%d expected=%d row=%d", len(payload), chunkBytes, expectedRow) } want := byte('A' + ((expectedRow - 1) % 26)) for i, b := range payload { if b != want { return fmt.Errorf("unexpected payload byte=%d value=%q expected=%q row=%d", i, b, want, expectedRow) } } return nil } func smallWorker(ctx context.Context, stopCh <-chan struct{}, cfg Config, id int, gauges *Gauges, events chan<- Event, wg *sync.WaitGroup) { defer wg.Done() r := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)*1000 + 1)) appName := fmt.Sprintf("pooler-stress-small-%d", id) if !maybeInitialStagger(ctx, stopCh, r, cfg.StartupStaggerMax) { return } conn, err := connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindSmall, WorkerID: id, Success: false, Err: "initial connect failed: " + shortErr(err), }) return } defer func() { closeConn(conn) }() for { if !canStartNewOp(ctx, stopCh) { return } gauges.SmallActive.Add(1) start := time.Now() qctx, cancel := context.WithTimeout(ctx, cfg.SmallQueryTimeout) var v int err := conn.QueryRow(qctx, "select 42").Scan(&v) ctxErr := qctx.Err() cancel() lat := time.Since(start) gauges.SmallActive.Add(-1) ev := Event{ Kind: KindSmall, WorkerID: id, Success: err == nil && v == 42, Timeout: errors.Is(ctxErr, context.DeadlineExceeded), Latency: lat, Limit: cfg.SmallMaxLatency, OverLimit: lat > cfg.SmallMaxLatency, } if err != nil { ev.Err = shortErr(err) } else if v != 42 { ev.Success = false ev.Err = fmt.Sprintf("bad result: %d", v) } if !sendEvent(ctx, events, ev) { return } if !ev.Success { return } if !canStartNewOp(ctx, stopCh) { return } if shouldReconnect(r, cfg.ReconnectProb) { closeConn(conn) conn, err = connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindSmall, WorkerID: id, Success: false, Err: "reconnect failed: " + shortErr(err), }) return } } if !sleepCtxOrStop(ctx, stopCh, randDuration(r, cfg.SmallThinkMin, cfg.SmallThinkMax)) { return } } } func preparedWorker(ctx context.Context, stopCh <-chan struct{}, cfg Config, id int, gauges *Gauges, events chan<- Event, wg *sync.WaitGroup) { defer wg.Done() r := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)*4000 + 4)) appName := fmt.Sprintf("pooler-stress-prepared-%d", id) const stmtName = "prep_small_rows" if !maybeInitialStagger(ctx, stopCh, r, cfg.StartupStaggerMax) { return } prepare := func(conn *pgx.Conn) error { pctx, cancel := context.WithTimeout(ctx, cfg.PreparedQueryTimeout) defer cancel() _, err := conn.Prepare(pctx, stmtName, ` select g.i, substr(md5((g.i + $1)::text), 1, 10) from generate_series(1, $2) as g(i) `) return err } conn, err := connectPreparedOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindPrepared, WorkerID: id, Success: false, Err: "initial connect failed: " + shortErr(err), }) return } defer func() { closeConn(conn) }() if err := prepare(conn); err != nil { sendEvent(ctx, events, Event{ Kind: KindPrepared, WorkerID: id, Success: false, Err: "initial prepare failed: " + shortErr(err), }) return } for { if !canStartNewOp(ctx, stopCh) { return } gauges.PreparedActive.Add(1) start := time.Now() qctx, cancel := context.WithTimeout(ctx, cfg.PreparedQueryTimeout) seed := r.Intn(1000000) rows, err := conn.Query(qctx, stmtName, seed, cfg.PreparedRows) success := true if err == nil { expectedRow := 1 n := 0 for rows.Next() { var rowNum int var s string if scanErr := rows.Scan(&rowNum, &s); scanErr != nil { err = scanErr success = false break } if validateErr := validatePreparedRow(expectedRow, rowNum, seed, s); validateErr != nil { err = validateErr success = false break } expectedRow++ n++ } if err == nil { if rowsErr := rows.Err(); rowsErr != nil { err = rowsErr success = false } else if n != cfg.PreparedRows { err = fmt.Errorf("unexpected row count=%d expected=%d", n, cfg.PreparedRows) success = false } } rows.Close() } else { success = false } ctxErr := qctx.Err() cancel() lat := time.Since(start) gauges.PreparedActive.Add(-1) ev := Event{ Kind: KindPrepared, WorkerID: id, Success: success && err == nil, Timeout: errors.Is(ctxErr, context.DeadlineExceeded), Latency: lat, Limit: cfg.PreparedMaxLatency, OverLimit: lat > cfg.PreparedMaxLatency, } if err != nil { ev.Err = shortErr(err) } if !sendEvent(ctx, events, ev) { return } if !ev.Success { return } if !canStartNewOp(ctx, stopCh) { return } if shouldReconnect(r, cfg.ReconnectProb) { closeConn(conn) conn, err = connectPreparedOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindPrepared, WorkerID: id, Success: false, Err: "reconnect failed: " + shortErr(err), }) return } if err := prepare(conn); err != nil { sendEvent(ctx, events, Event{ Kind: KindPrepared, WorkerID: id, Success: false, Err: "reprepare after reconnect failed: " + shortErr(err), }) return } } if !sleepCtxOrStop(ctx, stopCh, randDuration(r, cfg.PreparedThinkMin, cfg.PreparedThinkMax)) { return } } } func txWorker(ctx context.Context, stopCh <-chan struct{}, cfg Config, id int, gauges *Gauges, events chan<- Event, wg *sync.WaitGroup) { defer wg.Done() r := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)*2000 + 2)) appName := fmt.Sprintf("pooler-stress-tx-%d", id) if !maybeInitialStagger(ctx, stopCh, r, cfg.StartupStaggerMax) { return } conn, err := connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindTx, WorkerID: id, Success: false, Err: "initial connect failed: " + shortErr(err), }) return } defer func() { closeConn(conn) }() for { if !canStartNewOp(ctx, stopCh) { return } gauges.TxActive.Add(1) start := time.Now() qctx, cancel := context.WithTimeout(ctx, cfg.TxQueryTimeout) tx, err := conn.Begin(qctx) if err == nil { var v1 int err = tx.QueryRow(qctx, "select 1").Scan(&v1) if err == nil && v1 != 1 { err = fmt.Errorf("unexpected tx pre-sleep value=%d expected=1", v1) } if err == nil { _, err = tx.Exec(qctx, "select pg_sleep($1)", cfg.TxSleep.Seconds()) } if err == nil { var v2 int err = tx.QueryRow(qctx, "select 2").Scan(&v2) if err == nil && v2 != 2 { err = fmt.Errorf("unexpected tx post-sleep value=%d expected=2", v2) } } if err == nil { err = tx.Commit(qctx) } else { _ = tx.Rollback(context.Background()) } } ctxErr := qctx.Err() cancel() lat := time.Since(start) gauges.TxActive.Add(-1) ev := Event{ Kind: KindTx, WorkerID: id, Success: err == nil, Timeout: errors.Is(ctxErr, context.DeadlineExceeded), Latency: lat, Limit: cfg.TxMaxLatency, OverLimit: lat > cfg.TxMaxLatency, } if err != nil { ev.Err = shortErr(err) } if !sendEvent(ctx, events, ev) { return } if !ev.Success { return } if !canStartNewOp(ctx, stopCh) { return } if shouldReconnect(r, cfg.ReconnectProb) { closeConn(conn) conn, err = connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindTx, WorkerID: id, Success: false, Err: "reconnect failed: " + shortErr(err), }) return } } if !sleepCtxOrStop(ctx, stopCh, randDuration(r, cfg.TxThinkMin, cfg.TxThinkMax)) { return } } } var errElephantDropped = errors.New("elephant dropped mid-stream") func elephantWorker(ctx context.Context, stopCh <-chan struct{}, stopDeadline time.Time, cfg Config, id int, gauges *Gauges, events chan<- Event, wg *sync.WaitGroup) { defer wg.Done() r := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)*3000 + 3)) appName := fmt.Sprintf("pooler-stress-elephant-%d", id) if !maybeInitialStagger(ctx, stopCh, r, cfg.StartupStaggerMax) { return } conn, err := connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindElephant, WorkerID: id, Success: false, Err: "initial connect failed: " + shortErr(err), }) return } defer func() { closeConn(conn) }() limit := elephantLimit(cfg) for { if !canStartNewOp(ctx, stopCh) { return } // Don't start a new stream if there isn't enough time left before the // test ends for it to complete within the query timeout. Without this // guard the worker can start a ~20 s stream in the last seconds of the // test and then sit blocked until the 60 s context deadline fires, // causing a spurious timeout failure — especially visible under ASAN // where Odyssey runs slower. timeLeft := time.Until(stopDeadline) if timeLeft < cfg.ElephantQueryTimeout { return } gauges.ElephantActive.Add(1) start := time.Now() // Cap the stream context to the remaining test time so that even if // the timer fires mid-stream we exit cleanly instead of hanging until // the full ElephantQueryTimeout expires. streamTimeout := cfg.ElephantQueryTimeout if timeLeft < streamTimeout { streamTimeout = timeLeft } streamCtx, cancel := context.WithTimeout(ctx, streamTimeout) dropThisRun := r.Float64() < cfg.ElephantDropProb dropAt := 0 if cfg.ElephantChunks > 0 { dropAt = 1 + r.Intn(cfg.ElephantChunks) } err := func() error { rows, err := conn.Query( streamCtx, ` select g.i, repeat(chr(65 + ((g.i - 1) % 26)), $1) from generate_series(1, $2) as g(i) `, cfg.ElephantChunkBytes, cfg.ElephantChunks, ) if err != nil { return err } closedConnInside := false defer func() { if !closedConnInside { rows.Close() } }() expectedRow := 1 for rows.Next() { var rowNum int var b []byte if err := rows.Scan(&rowNum, &b); err != nil { return err } if err := validateElephantRow(expectedRow, rowNum, b, cfg.ElephantChunkBytes); err != nil { return err } gauges.ElephantBytes.Add(int64(len(b))) gauges.ElephantRows.Add(1) if dropThisRun && expectedRow == dropAt { closedConnInside = true closeConn(conn) conn = nil return errElephantDropped } expectedRow++ if !sleepCtx(streamCtx, cfg.ElephantRowDelay) { return streamCtx.Err() } } if err := rows.Err(); err != nil { return err } if expectedRow-1 != cfg.ElephantChunks { return fmt.Errorf("unexpected elephant row count=%d expected=%d", expectedRow-1, cfg.ElephantChunks) } return nil }() ctxErr := streamCtx.Err() cancel() lat := time.Since(start) gauges.ElephantActive.Add(-1) overLimit := false if err == nil && lat > limit { overLimit = true } ev := Event{ Kind: KindElephant, WorkerID: id, Success: err == nil, Timeout: errors.Is(ctxErr, context.DeadlineExceeded), Dropped: errors.Is(err, errElephantDropped), Latency: lat, Limit: limit, OverLimit: overLimit, } if err != nil { ev.Err = shortErr(err) } if !sendEvent(ctx, events, ev) { return } if ev.Dropped { if !canStartNewOp(ctx, stopCh) { return } conn, err = connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindElephant, WorkerID: id, Success: false, Err: "reconnect after intentional drop failed: " + shortErr(err), }) return } continue } if !ev.Success { return } if !canStartNewOp(ctx, stopCh) { return } if shouldReconnect(r, cfg.ReconnectProb) { closeConn(conn) conn, err = connectSimpleOnce(ctx, cfg.DSN, cfg.ConnectTimeout, appName) if err != nil { sendEvent(ctx, events, Event{ Kind: KindElephant, WorkerID: id, Success: false, Err: "reconnect failed: " + shortErr(err), }) return } } } } func validateConfig(cfg Config) error { switch { case cfg.DSN == "": return errors.New("dsn is required") case cfg.Duration <= 0: return errors.New("duration must be > 0") case cfg.ConnectTimeout <= 0: return errors.New("connect-timeout must be > 0") case cfg.ReconnectProb < 0 || cfg.ReconnectProb > 1: return errors.New("reconnect-prob must be in [0..1]") case cfg.StartupStaggerMax < 0: return errors.New("startup-stagger-max must be >= 0") case cfg.SmallClients < 0 || cfg.PreparedClients < 0 || cfg.TxClients < 0 || cfg.ElephantClients < 0: return errors.New("clients must be >= 0") case cfg.SmallThinkMax < cfg.SmallThinkMin: return errors.New("small-think-max must be >= small-think-min") case cfg.PreparedThinkMax < cfg.PreparedThinkMin: return errors.New("prepared-think-max must be >= prepared-think-min") case cfg.TxThinkMax < cfg.TxThinkMin: return errors.New("tx-think-max must be >= tx-think-min") case cfg.SmallQueryTimeout <= 0 || cfg.PreparedQueryTimeout <= 0 || cfg.TxQueryTimeout <= 0 || cfg.ElephantQueryTimeout <= 0: return errors.New("query timeouts must be > 0") case cfg.SmallMaxLatency <= 0 || cfg.PreparedMaxLatency <= 0 || cfg.TxMaxLatency <= 0: return errors.New("max latency limits must be > 0") case cfg.PreparedRows <= 0: return errors.New("prepared-rows must be > 0") case cfg.ElephantClients > 0 && (cfg.ElephantChunkBytes <= 0 || cfg.ElephantChunks <= 0): return errors.New("elephant chunk params must be > 0") case cfg.ElephantDropProb < 0 || cfg.ElephantDropProb > 1: return errors.New("elephant-drop-prob must be in [0..1]") case cfg.ElephantClients > 0 && cfg.ElephantMaxDuration <= 0 && cfg.ElephantRowDelay <= 0: return errors.New("for elephant clients: either elephant-max-duration > 0 or elephant-row-delay > 0") case cfg.ElephantClients > 0 && cfg.ElephantMaxDuration <= 0 && cfg.ElephantMaxFactor <= 0: return errors.New("elephant-max-factor must be > 0 if elephant-max-duration is not set") default: return nil } } func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) var cfg Config flag.StringVar(&cfg.DSN, "dsn", "", "Postgres DSN, usually point it to the pooler") flag.DurationVar(&cfg.Duration, "duration", 2*time.Minute, "test duration; after this moment no new operations are started") flag.DurationVar(&cfg.ConnectTimeout, "connect-timeout", 5*time.Second, "connect timeout") flag.BoolVar(&cfg.FailFast, "fail-fast", true, "stop immediately on first SLA violation or unexpected error") flag.Float64Var(&cfg.ReconnectProb, "reconnect-prob", 0.05, "probability to close and reopen a healthy connection after each completed operation") flag.DurationVar(&cfg.StartupStaggerMax, "startup-stagger-max", 0, "random initial delay in [0..value] before worker starts") flag.IntVar(&cfg.SmallClients, "small-clients", 200, "number of small clients") flag.DurationVar(&cfg.SmallThinkMin, "small-think-min", 20*time.Millisecond, "small client think time min") flag.DurationVar(&cfg.SmallThinkMax, "small-think-max", 100*time.Millisecond, "small client think time max") flag.DurationVar(&cfg.SmallQueryTimeout, "small-timeout", 2*time.Second, "small query timeout") flag.DurationVar(&cfg.SmallMaxLatency, "small-max-latency", 500*time.Millisecond, "small query max allowed latency") flag.IntVar(&cfg.PreparedClients, "prepared-clients", 20, "number of prepared-statement clients") flag.IntVar(&cfg.PreparedRows, "prepared-rows", 10, "rows returned by prepared query") flag.DurationVar(&cfg.PreparedThinkMin, "prepared-think-min", 20*time.Millisecond, "prepared client think time min") flag.DurationVar(&cfg.PreparedThinkMax, "prepared-think-max", 100*time.Millisecond, "prepared client think time max") flag.DurationVar(&cfg.PreparedQueryTimeout, "prepared-timeout", 2*time.Second, "prepared query timeout") flag.DurationVar(&cfg.PreparedMaxLatency, "prepared-max-latency", 500*time.Millisecond, "prepared query max allowed latency") flag.IntVar(&cfg.TxClients, "tx-clients", 5, "number of tx clients") flag.DurationVar(&cfg.TxSleep, "tx-sleep", 1*time.Second, "sleep inside transaction") flag.DurationVar(&cfg.TxThinkMin, "tx-think-min", 100*time.Millisecond, "tx think time min") flag.DurationVar(&cfg.TxThinkMax, "tx-think-max", 500*time.Millisecond, "tx think time max") flag.DurationVar(&cfg.TxQueryTimeout, "tx-timeout", 5*time.Second, "tx timeout") flag.DurationVar(&cfg.TxMaxLatency, "tx-max-latency", 1500*time.Millisecond, "tx max allowed latency") flag.IntVar(&cfg.ElephantClients, "elephant-clients", 3, "number of elephant clients") flag.IntVar(&cfg.ElephantChunkBytes, "elephant-chunk-bytes", 64*1024, "bytes per elephant row/chunk") flag.IntVar(&cfg.ElephantChunks, "elephant-chunks", 20, "rows/chunks per elephant stream") flag.DurationVar(&cfg.ElephantRowDelay, "elephant-row-delay", 1*time.Second, "sleep between rows for elephant") flag.Float64Var(&cfg.ElephantDropProb, "elephant-drop-prob", 0.10, "probability of dropping elephant connection mid-stream") flag.DurationVar(&cfg.ElephantQueryTimeout, "elephant-timeout", 40*time.Second, "elephant stream timeout") flag.Float64Var(&cfg.ElephantMaxFactor, "elephant-max-factor", 1.2, "if elephant-max-duration=0, allowed elephant duration = chunks * row_delay * factor") flag.DurationVar(&cfg.ElephantMaxDuration, "elephant-max-duration", 0, "optional explicit elephant max duration; overrides factor-based limit") flag.Parse() if err := validateConfig(cfg); err != nil { log.Fatalf("invalid config: %v", err) } log.Printf( "starting: duration=%s small=%d prepared=%d tx=%d elephant=%d elephant_stream≈%s elephant_limit=%s fail_fast=%v reconnect_prob=%.3f startup_stagger_max=%s", cfg.Duration, cfg.SmallClients, cfg.PreparedClients, cfg.TxClients, cfg.ElephantClients, formatBytes(int64(cfg.ElephantChunkBytes*cfg.ElephantChunks)), elephantLimit(cfg), cfg.FailFast, cfg.ReconnectProb, cfg.StartupStaggerMax, ) runCtx, cancel := context.WithCancel(context.Background()) defer cancel() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { select { case sig := <-sigCh: log.Printf("got signal %s, stopping...", sig) cancel() case <-runCtx.Done(): } }() stopDeadline := time.Now().Add(cfg.Duration) stopCh := make(chan struct{}) go func() { t := time.NewTimer(cfg.Duration) defer t.Stop() select { case <-t.C: close(stopCh) case <-runCtx.Done(): } }() gauges := &Gauges{} events := make(chan Event, 65536) collector := newCollector(cfg, gauges, cancel) collectorDone := make(chan struct{}) go collector.run(events, collectorDone) var wg sync.WaitGroup for i := 0; i < cfg.SmallClients; i++ { wg.Add(1) go smallWorker(runCtx, stopCh, cfg, i, gauges, events, &wg) } for i := 0; i < cfg.PreparedClients; i++ { wg.Add(1) go preparedWorker(runCtx, stopCh, cfg, i, gauges, events, &wg) } for i := 0; i < cfg.TxClients; i++ { wg.Add(1) go txWorker(runCtx, stopCh, cfg, i, gauges, events, &wg) } for i := 0; i < cfg.ElephantClients; i++ { wg.Add(1) go elephantWorker(runCtx, stopCh, stopDeadline, cfg, i, gauges, events, &wg) } wg.Wait() close(events) <-collectorDone if collector.failed { os.Exit(2) } } odyssey-1.6.0-rc1/test/stress/primary-init/000077500000000000000000000000001526064202200206255ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/stress/primary-init/01-append-hba.sh000066400000000000000000000003361526064202200234000ustar00rootroot00000000000000#!/bin/bash set -eux sed -i "s/host all all all scram-sha-256//g" "$PGDATA/pg_hba.conf" echo "host replication replicator all md5" >> "$PGDATA/pg_hba.conf" echo "host all all all scram-sha-256" >> "$PGDATA/pg_hba.conf" odyssey-1.6.0-rc1/test/stress/primary-init/02-set-max-connections.sh000066400000000000000000000001531526064202200252750ustar00rootroot00000000000000#!/bin/bash set -eux sed -i "s/max_connections = 100/max_connections = 400/g" "$PGDATA/postgresql.conf" odyssey-1.6.0-rc1/test/stress/primary-init/03-create-users.sql000066400000000000000000000010741526064202200241720ustar00rootroot00000000000000CREATE USER suser WITH PASSWORD 'postgres'; GRANT ALL ON DATABASE postgres TO suser; GRANT ALL ON SCHEMA public TO suser; CREATE USER tuser WITH PASSWORD 'postgres'; GRANT ALL ON DATABASE postgres TO tuser; GRANT ALL ON SCHEMA public TO tuser; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT,INSERT,UPDATE,DELETE ON TABLES TO suser, tuser; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE,SELECT ON SEQUENCES TO suser, tuser; CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'postgres'; SELECT pg_create_physical_replication_slot('replication_slot'); odyssey-1.6.0-rc1/test/stress/primary-init/04-log-locks.sql000066400000000000000000000002351526064202200234610ustar00rootroot00000000000000ALTER SYSTEM SET log_min_duration_statement = 5000; ALTER SYSTEM SET log_lock_waits = on; ALTER SYSTEM SET deadlock_timeout = '1s'; SELECT pg_reload_conf(); odyssey-1.6.0-rc1/test/stress/primary-init/05-hot-standby-feedback.sql000066400000000000000000000001051526064202200255420ustar00rootroot00000000000000ALTER SYSTEM SET hot_standby_feedback = on; SELECT pg_reload_conf(); odyssey-1.6.0-rc1/test/stress/replica-init/000077500000000000000000000000001526064202200205615ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/stress/replica-init/10-setup-standby.sh000066400000000000000000000004741526064202200241420ustar00rootroot00000000000000#!/bin/bash set -eux until pg_isready -h primary -p 5432 -U replicator -d postgres; do echo "Wait for primary..." sleep 1 done pg_ctl -D "$PGDATA" -m fast -w stop rm -rf "$PGDATA"/* PGPASSWORD="postgres" pg_basebackup -h primary -D "$PGDATA" -U replicator -Fp -Xs -P -R pg_ctl -D "$PGDATA" -m fast -w start odyssey-1.6.0-rc1/test/unit/000077500000000000000000000000001526064202200156355ustar00rootroot00000000000000odyssey-1.6.0-rc1/test/unit/Dockerfile000066400000000000000000000016111526064202200176260ustar00rootroot00000000000000FROM ubuntu:noble ARG build_type=build_release RUN apt update && apt install -y ca-certificates RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources && \ sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.yandex.ru/ubuntu|g' \ /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ git \ libssl-dev \ libldap-common \ openssl \ libpam0g-dev \ libldap-dev \ build-essential \ cmake \ clang \ bison flex \ libpthread-stubs0-dev libclang-rt-dev RUN mkdir /odyssey WORKDIR /odyssey COPY . ./ ARG odyssey_cc ENV CC=${odyssey_cc} RUN make clean RUN make ${build_type} WORKDIR /odyssey/build/sources ENTRYPOINT [ "/odyssey/build/sources/odyssey_test" ]