pax_global_header00006660000000000000000000000064151770030350014513gustar00rootroot0000000000000052 comment=6fedbae2f3686ad38c4d563bea55b8c487e38078 odyssey-1.5.1-rc8/000077500000000000000000000000001517700303500137105ustar00rootroot00000000000000odyssey-1.5.1-rc8/.clang-format000066400000000000000000000077271517700303500163000ustar00rootroot00000000000000# 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.5.1-rc8/.dockerignore000066400000000000000000000000521517700303500163610ustar00rootroot00000000000000build prometheus/exporter/odyssey_exporterodyssey-1.5.1-rc8/.git-blame-ignore-revs000066400000000000000000000000521517700303500200050ustar00rootroot00000000000000f0ce56ec3b3d8b742bd15a59ca35c781488c27dc odyssey-1.5.1-rc8/.github/000077500000000000000000000000001517700303500152505ustar00rootroot00000000000000odyssey-1.5.1-rc8/.github/dependabot.yml000066400000000000000000000012021517700303500200730ustar00rootroot00000000000000version: 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.5.1-rc8/.github/workflows/000077500000000000000000000000001517700303500173055ustar00rootroot00000000000000odyssey-1.5.1-rc8/.github/workflows/async-notification.yml000066400000000000000000000010341517700303500236270ustar00rootroot00000000000000name: Async notification proxy on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: async-notification: name: async-notification runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Test with async notifications against Odyssey run: make async-notice-test odyssey-1.5.1-rc8/.github/workflows/build.yml000066400000000000000000000034401517700303500211300ustar00rootroot00000000000000name: build on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "CMakeLists.txt" - "test/**" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" 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@v6 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@v6 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@v6 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', 'bullseye', 'trixie', 'forky', 'sid'] build-type: ['build_release'] steps: - uses: actions/checkout@v6 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 }} odyssey-1.5.1-rc8/.github/workflows/coverity.yml000066400000000000000000000025641517700303500217030ustar00rootroot00000000000000name: 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@v6 - name: Cache Coverity build tool uses: actions/cache@v5 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.5.1-rc8/.github/workflows/docker-dev.yml000066400000000000000000000036421517700303500220600ustar00rootroot00000000000000name: Docker Dev Build on: push: branches: - master pull_request: branches: - master 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@v6 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 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.5.1-rc8/.github/workflows/docker-release.yml000066400000000000000000000035221517700303500227170ustar00rootroot00000000000000name: 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@v6 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 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.5.1-rc8/.github/workflows/drivers-tests.yml000066400000000000000000000015021517700303500226440ustar00rootroot00000000000000name: Drivers tests on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: jdbc: name: jdbc runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 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@v6 with: submodules: recursive - name: docker buildx uses: docker/setup-buildx-action@v4 - name: Odyssey userver compatibility tests run: make userver_test odyssey-1.5.1-rc8/.github/workflows/format.yml000066400000000000000000000004421517700303500213200ustar00rootroot00000000000000name: Format on: push: branches: [ master ] pull_request: branches: [ master ] jobs: format: name: format runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Check format run: make check-format odyssey-1.5.1-rc8/.github/workflows/functional-tests.yml000066400000000000000000000013361517700303500233350ustar00rootroot00000000000000name: Functional tests on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" 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@v6 with: submodules: recursive - name: Odyssey functional tests run: make functional-test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} ODYSSEY_CC=${{ matrix.cc }} odyssey-1.5.1-rc8/.github/workflows/prom-exporter-test.yml000066400000000000000000000006401517700303500236300ustar00rootroot00000000000000name: Check prometheus exporter work on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "docker/prom-exporter/**" - "prometheus/**" jobs: prom-exporter: name: prom-exporter runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Check prom exporter works run: make prom-exporter-test odyssey-1.5.1-rc8/.github/workflows/prometheus-test.yml000066400000000000000000000012001517700303500231710ustar00rootroot00000000000000name: Build with prometheus on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: prom: name: prom runs-on: ubuntu-latest strategy: matrix: build-type: ['build_release', 'build_dbg', 'build_asan'] steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Odyssey build with prometheus test run: make prometheus-legacy-test ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} odyssey-1.5.1-rc8/.github/workflows/proto-tests.yml000066400000000000000000000014341517700303500223350ustar00rootroot00000000000000name: Protocol tests on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" 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@v6 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.5.1-rc8/.github/workflows/quickstart-alpine-test.yml000066400000000000000000000010171517700303500244440ustar00rootroot00000000000000name: Quickstart image test on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: quickstart-image-build: name: quickstart-image-build runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Odyssey quickstart image test run: make quickstart_test odyssey-1.5.1-rc8/.github/workflows/replication-test.yml000066400000000000000000000010161517700303500233140ustar00rootroot00000000000000name: Replication test on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: replication-test: name: replication-test runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Replication protocol with Odyssey image test run: make replication_test odyssey-1.5.1-rc8/.github/workflows/soft-oom.yml000066400000000000000000000007331517700303500215760ustar00rootroot00000000000000name: Soft oom on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "docker/**" - "CMakeLists.txt" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: soft_oom: name: soft_oom runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Odyssey soft oom test run: make soft-oom-testodyssey-1.5.1-rc8/.github/workflows/stress-tests.yml000066400000000000000000000011761517700303500225200ustar00rootroot00000000000000name: Stress tests on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" 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@v6 with: submodules: recursive - name: Odyssey stress tests run: make stress-tests ODYSSEY_BUILD_TYPE=${{ matrix.build-type }} odyssey-1.5.1-rc8/.github/workflows/unit-tests.yml000066400000000000000000000015631517700303500221540ustar00rootroot00000000000000name: Unit tests on: push: branches: [ master ] pull_request: branches: [ master ] paths: - "sources/**" - "test/**" - "CMakeLists.txt" - "docker/**" - "modules/**" - "cmake/**" - "!docker/prom-exporter/**" jobs: unit: name: unit runs-on: ubuntu-latest strategy: matrix: build-type: ['build_release', 'build_dbg', 'build_asan'] target-arch: ['linux/amd64'] cc: ['gcc', 'clang'] steps: - uses: actions/checkout@v6 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.5.1-rc8/.gitignore000066400000000000000000000016651517700303500157100ustar00rootroot00000000000000*.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.5.1-rc8/.gitmodules000066400000000000000000000000001517700303500160530ustar00rootroot00000000000000odyssey-1.5.1-rc8/AUTHORS000066400000000000000000000004351517700303500147620ustar00rootroot00000000000000The 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.5.1-rc8/CMakeLists.txt000066400000000000000000000206221517700303500164520ustar00rootroot00000000000000cmake_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) 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(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_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 " 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") 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") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS} -fsanitize=address,undefined,leak -fno-sanitize-recover=all -fno-omit-frame-pointer -O0") 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) set(od_libraries "rt" "dl") 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.5.1-rc8/LICENSE000066400000000000000000000026641517700303500147250ustar00rootroot00000000000000 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.5.1-rc8/Makefile000066400000000000000000000243771517700303500153650ustar00rootroot00000000000000BUILD_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') 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) --verbose --console --log_to_stdout 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 -c "%u:%g" .` -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 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-dev-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-dev-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-dev-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) \ 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 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 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.5.1-rc8/README.md000066400000000000000000000061531517700303500151740ustar00rootroot00000000000000



## 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 Optional dependencies: * libsystemd-dev (for systemd notify support) And for fedora-based distros: * openssl-devel * systemd-devel (optional, for systemd notify) ```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.5.1-rc8/VERSION000066400000000000000000000000121517700303500147510ustar00rootroot000000000000001.5.1-rc8 odyssey-1.5.1-rc8/benchmarks/000077500000000000000000000000001517700303500160255ustar00rootroot00000000000000odyssey-1.5.1-rc8/benchmarks/client_max_routing/000077500000000000000000000000001517700303500217175ustar00rootroot00000000000000odyssey-1.5.1-rc8/benchmarks/client_max_routing/go.mod000066400000000000000000000002231517700303500230220ustar00rootroot00000000000000module catchuploadtest go 1.25.0 require ( github.com/lib/pq v1.12.3 golang.org/x/term v0.42.0 ) require golang.org/x/sys v0.43.0 // indirect odyssey-1.5.1-rc8/benchmarks/client_max_routing/go.sum000066400000000000000000000007171517700303500230570ustar00rootroot00000000000000github.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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= odyssey-1.5.1-rc8/benchmarks/client_max_routing/main.go000066400000000000000000000170541517700303500232010ustar00rootroot00000000000000package 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.5.1-rc8/cmake/000077500000000000000000000000001517700303500147705ustar00rootroot00000000000000odyssey-1.5.1-rc8/cmake/FindBoringSSL.cmake000066400000000000000000000035161517700303500204020ustar00rootroot00000000000000# - 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.5.1-rc8/cmake/FindLDAP.cmake000066400000000000000000000005011517700303500173070ustar00rootroot00000000000000 # - Try to find the LDAP libraries # # LDAP_FOUND - ldap was successfully found # LDAP_INCLUDE_DIR - ldap include directory # LDAP_LIBRARIES - ldap libraries find_path(LDAP_INCLUDE_DIR NAMES ldap.h) find_library(LDAP_LIBRARY ldap) find_package_handle_standard_args(LDAP REQUIRED_VARS LDAP_LIBRARY LDAP_INCLUDE_DIR) odyssey-1.5.1-rc8/cmake/FindPAM.cmake000066400000000000000000000005231517700303500172100ustar00rootroot00000000000000 # - 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.5.1-rc8/cmake/FindProm.cmake000066400000000000000000000005111517700303500175050ustar00rootroot00000000000000# - 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.5.1-rc8/cmake/FindPromhttp.cmake000066400000000000000000000005751517700303500204170ustar00rootroot00000000000000# - 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.5.1-rc8/cmake/FindZstd.cmake000066400000000000000000000022621517700303500175210ustar00rootroot00000000000000# 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.5.1-rc8/cmake/Packaging.cmake000066400000000000000000000051201517700303500176540ustar00rootroot00000000000000set(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.5.1-rc8/config-examples/000077500000000000000000000000001517700303500167715ustar00rootroot00000000000000odyssey-1.5.1-rc8/config-examples/odyssey-aq.conf000066400000000000000000000033321517700303500217370ustar00rootroot00000000000000pid_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.5.1-rc8/config-examples/odyssey-dev-ldap.conf000066400000000000000000000132611517700303500230340ustar00rootroot00000000000000pid_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.5.1-rc8/config-examples/odyssey-dev-with-watchdog.conf000066400000000000000000000067361517700303500246760ustar00rootroot00000000000000pid_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.5.1-rc8/config-examples/odyssey-dev.conf000066400000000000000000000031461517700303500221170ustar00rootroot00000000000000pid_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" graceful_die_on_errors yes enable_online_restart no bindwith_reuseport yesodyssey-1.5.1-rc8/debian/000077500000000000000000000000001517700303500151325ustar00rootroot00000000000000odyssey-1.5.1-rc8/debian/changelog000066400000000000000000000046111517700303500170060ustar00rootroot00000000000000odyssey (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.5.1-rc8/debian/control000066400000000000000000000022671517700303500165440ustar00rootroot00000000000000Source: 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 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.5.1-rc8/debian/copyright000066400000000000000000000032041517700303500170640ustar00rootroot00000000000000Upstream-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.5.1-rc8/debian/rules000077500000000000000000000004621517700303500162140ustar00rootroot00000000000000#!/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.5.1-rc8/debian/source/000077500000000000000000000000001517700303500164325ustar00rootroot00000000000000odyssey-1.5.1-rc8/debian/source/format000066400000000000000000000000151517700303500176410ustar00rootroot000000000000003.0 (native) odyssey-1.5.1-rc8/docker-compose.yml000066400000000000000000000012401517700303500173420ustar00rootroot00000000000000version: '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.5.1-rc8/docker/000077500000000000000000000000001517700303500151575ustar00rootroot00000000000000odyssey-1.5.1-rc8/docker/dev-envs/000077500000000000000000000000001517700303500167065ustar00rootroot00000000000000odyssey-1.5.1-rc8/docker/dev-envs/Dockerfile.ppc64el000066400000000000000000000023131517700303500221530ustar00rootroot00000000000000# 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 \ 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.5.1-rc8/docker/dpkg/000077500000000000000000000000001517700303500161045ustar00rootroot00000000000000odyssey-1.5.1-rc8/docker/dpkg/Dockerfile.bionic000066400000000000000000000023711517700303500213430ustar00rootroot00000000000000FROM 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 \ 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.5.1-rc8/docker/dpkg/Dockerfile.jammy000066400000000000000000000023611517700303500212140ustar00rootroot00000000000000FROM 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 \ 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.5.1-rc8/docker/dpkg/copy-package-files.sh000077500000000000000000000002121517700303500221010ustar00rootroot00000000000000#!/bin/bash set -eux mkdir -p /packages for result in $(find / -maxdepth 1 -name "odyssey*" -type f); do cp $result /packages done odyssey-1.5.1-rc8/docker/dpkg/runner.sh000077500000000000000000000016501517700303500177560ustar00rootroot00000000000000#!/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.5.1-rc8/docker/format/000077500000000000000000000000001517700303500164475ustar00rootroot00000000000000odyssey-1.5.1-rc8/docker/format/Dockerfile000066400000000000000000000007551517700303500204500ustar00rootroot00000000000000FROM ubuntu:noble 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 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.5.1-rc8/docker/format/run-format.sh000066400000000000000000000001421517700303500210720ustar00rootroot00000000000000#!/bin/bash /run-clang-format/run-clang-format.py -r --clang-format-executable clang-format-18 $@odyssey-1.5.1-rc8/docker/quickstart/000077500000000000000000000000001517700303500173515ustar00rootroot00000000000000odyssey-1.5.1-rc8/docker/quickstart/Dockerfile000066400000000000000000000023321517700303500213430ustar00rootroot00000000000000FROM 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 \ && 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.5.1-rc8/docker/quickstart/config.conf000066400000000000000000000011461517700303500214670ustar00rootroot00000000000000unix_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.5.1-rc8/docker/quickstart/entrypoint.sh000066400000000000000000000126441517700303500221270ustar00rootroot00000000000000#!/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 | | 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. | | 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) | Maximum number of catchup checks; 0 = no limit. | ## **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 } ``` --- ## **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\_string setting. --- ## **pool\_discard\_string** *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` --- ## **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_reserve_prepared_statement** *yes|no* Enable support of prepared statements in transactional pooling. `pool_reserve_prepared_statement yes` ## **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 - 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** *integer* Maximum number of catchup checks before closing the connection if the host replication lag is too big See [catchup-timeout.md](../features/catchup-timeout.md) for more details `catchup_checks 10` --- ## 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.5.1-rc8/docs/configuration/shared-pools.md000066400000000000000000000003621517700303500224320ustar00rootroot00000000000000# 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.5.1-rc8/docs/configuration/soft-oom.md000066400000000000000000000016471517700303500216040ustar00rootroot00000000000000# 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.5.1-rc8/docs/configuration/storage.md000066400000000000000000000045771517700303500215120ustar00rootroot00000000000000# 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" - switch to TLS protocol on request "require" - TLS required "verify_ca" - require valid certificate "verify_full" - require valid certificate ``` ## **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` ## **watchdog** 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. ```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 # 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 } ``` ## 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.5.1-rc8/docs/development/000077500000000000000000000000001517700303500171625ustar00rootroot00000000000000odyssey-1.5.1-rc8/docs/development/debugging.md000066400000000000000000000117421517700303500214440ustar00rootroot00000000000000# 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.5.1-rc8/docs/development/internals.md000066400000000000000000000202021517700303500214770ustar00rootroot00000000000000# 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.5.1-rc8/docs/development/local-runs.md000066400000000000000000000023501517700303500215630ustar00rootroot00000000000000# Running things locally --- 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.5.1-rc8/docs/development/packaging.md000066400000000000000000000012001517700303500214210ustar00rootroot00000000000000# Packaging ---- 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.5.1-rc8/docs/development/testing.md000066400000000000000000000034171517700303500211660ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/000077500000000000000000000000001517700303500164565ustar00rootroot00000000000000odyssey-1.5.1-rc8/docs/features/balancing.md000066400000000000000000000031451517700303500207210ustar00rootroot00000000000000# 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. 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.odyssey-1.5.1-rc8/docs/features/catchup-timeout.md000066400000000000000000000033211517700303500221120ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/console.md000066400000000000000000000051521517700303500204450ustar00rootroot00000000000000# 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` ### 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 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 ``` ## 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.5.1-rc8/docs/features/online-restart.md000066400000000000000000000067031517700303500217540ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/pause.md000066400000000000000000000037331517700303500201230ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/pooling.md000066400000000000000000000053501517700303500204520ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/prometheus-metrics.md000066400000000000000000000151051517700303500226410ustar00rootroot00000000000000# 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 blocked waiting for a server connection. | | `odyssey_client_pool_maxwait_seconds_route` | `user`, `database` | Gauge | Maximum observed wait 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 | TCP connections opened toward the backend. | | `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"}) ``` 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 | **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.5.1-rc8/docs/features/shared-pools.md000066400000000000000000000024261517700303500214040ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/soft-oom.md000066400000000000000000000032031517700303500205410ustar00rootroot00000000000000# 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.5.1-rc8/docs/features/tsa.md000066400000000000000000000060021517700303500175650ustar00rootroot00000000000000# 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 - `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.5.1-rc8/docs/img/000077500000000000000000000000001517700303500154145ustar00rootroot00000000000000odyssey-1.5.1-rc8/docs/img/balancing.svg000066400000000000000000001667111517700303500200670ustar00rootroot00000000000000 wild clientsOdyssey asbalancerpostgresinstance 1postgresinstance 2postgresinstance 3odyssey-1.5.1-rc8/docs/img/favicon.ico000066400000000000000000007774761517700303500175710ustar00rootroot00000000000000 (( #.#. "+5?CB>:4$"3G]poK,7Y<#+EiiP>-{c]E, *Qz_&E{xfH/  .GgS% >p^M;+! *6?JZsk3 )_`NA9/&$'.2,'#  $;Xyl&.wm?1KoC 8b^E- 8T$Aea,  QJ)?oXrt#Z? 9eL?g|.u;&?,lQCJ+)9I]s m8.gfJ 0lQ5'\pG VpD' u|Us*  TwM:;<5& MjO5 ,P_@& *Xe:C; mdRH?<>DNND>;6/' !.C[v_.y{mRL6 &<XD -p|M'Qn #IyR\/Tc$ ECRk bvDP[/M3_]=%jY&Sh)R-s,Ev`hQ<w+NW) S8Tbr9<C"!b3eo}/=(]f: [:gS6.WX8 Dl;U} 8<)!@~tQ-J9( 3w{[(iX# 2lI>r"#_>=C"Ih m ^ 'Dk tpo #EFI;3d5RFW  O:C TFD ?V jGKy[S 4< tm Zc\ %+K, bnN'/LQ=l;06"Ok ~(*.%5cU? E4Ex<9 C Id "q%GhC`;  )=l2 %v^[?Hz #:a\6a68 R=R]#k/l:KQ*&[v?*bO7$~ 7'1W46k$C&J~mU]!MEhvl7PmE +?"q2FunrapR1+TZj{YZs  FbA$6 Qr5o{'f|Lj1l V eI`#2|(zXo) b)ULV,K1>:42.8$+p(J}%Vq"{%Av-&0Cz.,FJ(!C/t 7 s:}9Xr4[h 0MH$^`zA 9l$]8M:k A ????????????? ????????????????????????????|||<<<  ??odyssey-1.5.1-rc8/docs/img/odyssey.png000066400000000000000000000162621517700303500176300ustar00rootroot00000000000000PNG  IHDR<2glvbKGD pHYs B(xtIME >?IDATxy\eo' % "K0a **3 *:eFwQTqTY( \-` {kstytַ[{$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Ie3S^#<Rih#楟 <<)4\}8?qIuxFpΫ{ŞI,f`10SHp0xG lWkX 橒4 <G2"37Hk3sW9xLP5SjݚZ{^)' UH^,x̒8<%iH9XZnOoy8-=u߉y\/,;z xPq b7S`,@b%$$bn]e :I<1%F[xPɪ߳<<^J? |s Uր'5 \G@Eŗxn0涙<TV' 3? k[STdJe oW{Y >ȉWm*0SkPଌۿkt xRMƎmW $yIkJe.gMNOpgAT"cf~;hՇ7O*33n`SU3.se:c~l` O*#S3n %2Ȁ'E-RTR# 5׼FO-*[xR^,ȹk6%d/e{Xn5ImlcYn)pn[xR O׮~ xMsߟ4v"SVbYZVYlI]wwwg)ǐwv$5k PΥl/fS~Hv3:I`Ep<1Ϯ\{[u1 xu|SǬ:Iͺ =I~v6lYu` Cg$*VF V)_=]F߷,.O2NlIY}5Ö׺3nJjԇiYdFIVFL(&:\YW`n9pU#>HсJ-ğymĠɥĚYIj)^+DKdRP>'޵v]-uZ\-;d$>EqSIњD3#g8nL&pSIpw[ s_S0?4Im09/D[=7n%iHj )N.34+4KjS,#E<7Jw gL) "? rI ֩(H= xtI[t!+Ќ"ֹ}SKcVӭIel 2xa= &v?UR+M ֢ w7#W~D߾9rk<r4ź|obsFձ'XnbIy%>EL![NvvbY*KRVYYYݮ"Sǯ'z8$$Tb/H-M:7hp"1WyT]~c cIjnDψ kilo ѧ75X-QBqW۪ԨĔeV[mb9iD*D]^uدSkb2a?#R0|f n~Gd9b2&1Z4>끳lI2{1P嶉|HيMjy;ef'[jM{덪> 'ha9nїdv5=vNvo#jDSZ( Vl"~I]KUoV= )ֻ2?A `.p4V˩uz5jJ NL佭N4gSvW7$`Y#;|~I|x0\D ]p1АPW}r{l7~G:rx9їx+jUϻqrcœ@h2kR7%upŎks_۲6|DkOI-4kU;SI"t*;lWk R`y*C3+K'6sJ-V]n%\D:,$Ad9ԏ1E"ifޒ` p.1& mݕ$'x11Fۦ1kηy>!];iXn`7$+\Odo9:tpkjQnM &c 10ʱO}|j ؊_wD6É5QRVVOV petlJ?@9Ha>  VK$_JLLY.++6`طb]:6{]}GL"_DlRM2Fڕ<*2/3|ҷ Ϧ'Zbaib#jn_Bd۾Sj=)P dq.",f u_"<ޏXrK6V >t=k[;؃b&1yOgYD+=)Nqc*)t+=*sSP,*iȷa"I`ؐItz6;nui4%O̧kd`i zOb22D2FGW@dԏ@"TO2{5ouDRI]RAn#UeATF'_gRKJvĥ:1b}ޞBIC!L-l-~ kkNP0!"f@](I7kDfz9bu^۞> JcKmrd9888d=R|8'foJwnN1Ns=-^5dVv!FJl<= ܝߧ˴˾9*Rkm|jK-YtG (kn#,"|- 鋱o$=89}yZt}ejojLH-nko-e3DZ !{g ڨt۶tT{{#WYZsszNTZ3u#J{+ o&6Âo89xi̙.U'Rv яVݧgֿ70&)KBD쓪-U *u3i(RZЗ ;=nk y؜9LNs 6""⪖*Áo8x\qAǮ$˜U_ oE\]Ղcj.FWƦX sRu1ز\ԑqްeX.aVȪ?)mI-Z۫.ӆJ'w ~}Y31i=@ݯo8Im<9}o!r=C_TuR0?R+pL:`86~0,`<I] S=K;EtwU6uU}*sƦ Žӱ/ѯnR0? jK;%ZINOǬg+#jjgge .^oT2F}՛Tl$2I$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$GLIENDB`odyssey-1.5.1-rc8/docs/img/odyssey.svg000066400000000000000000000335011517700303500176360ustar00rootroot00000000000000 odyssey-1.5.1-rc8/docs/img/online-restart.svg000066400000000000000000001314411517700303500211070ustar00rootroot00000000000000 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.5.1-rc8/docs/index.md000066400000000000000000000046761517700303500163060ustar00rootroot00000000000000# 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.5.1-rc8/docs/nginx.conf000066400000000000000000000060021517700303500166300ustar00rootroot00000000000000worker_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.5.1-rc8/docs/quick-start.md000066400000000000000000000264021517700303500174350ustar00rootroot00000000000000# 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.5.1-rc8/docs/tf/000077500000000000000000000000001517700303500152515ustar00rootroot00000000000000odyssey-1.5.1-rc8/docs/tf/terraform.tfvars.example000066400000000000000000000002031517700303500221260ustar00rootroot00000000000000folder_id = "folder-id" domain = "my.odyssey.web.domain" # with editor/admin role for storages sa_id = "service-account-id"odyssey-1.5.1-rc8/docs/tf/web.tf000066400000000000000000000035711517700303500163670ustar00rootroot00000000000000variable "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.5.1-rc8/mkdocs.yml000066400000000000000000000102441517700303500157140ustar00rootroot00000000000000site_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.5.1-rc8/modules/000077500000000000000000000000001517700303500153605ustar00rootroot00000000000000odyssey-1.5.1-rc8/modules/Makefile000066400000000000000000000003421517700303500170170ustar00rootroot00000000000000PATH_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.5.1-rc8/modules/audit_module.c000066400000000000000000000023161517700303500202010ustar00rootroot00000000000000/* * 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.5.1-rc8/modules/audit_module.h000066400000000000000000000006211517700303500202030ustar00rootroot00000000000000#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.5.1-rc8/odyssey.conf000066400000000000000000000411131517700303500162560ustar00rootroot00000000000000### ### 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" # # # 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.5.1-rc8/prometheus/000077500000000000000000000000001517700303500161035ustar00rootroot00000000000000odyssey-1.5.1-rc8/prometheus/exporter/000077500000000000000000000000001517700303500177535ustar00rootroot00000000000000odyssey-1.5.1-rc8/prometheus/exporter/exporter.go000066400000000000000000001026131517700303500221550ustar00rootroot00000000000000// 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;" 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 idle (not actively using a server)", []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), } describeMetricDescs = []*prometheus.Desc{ versionDescription, exporterUpDescription, isPausedDescription, avgTxCountDescription, avgQueryCountDescription, avgRecvBytesPerSecondDescription, avgSentBytesPerSecondDescription, avgXactTimeSecondsDescription, avgQueryTimeSecondsDescription, avgWaitTimeSecondsDescription, clientPoolActiveRouteDescription, clientPoolWaitingRouteDescription, 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, }, "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("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 } func (exporter *Exporter) sendListsMetrics(ctx context.Context, ch chan<- prometheus.Metric, db *sql.DB) error { rows, err := db.QueryContext(ctx, showListsCommand) if err != nil { return fmt.Errorf("error getting version: %w", err) } defer rows.Close() columns, err := rows.Columns() if err != nil { return fmt.Errorf("can't get columns of lists") } if len(columns) != 2 || columns[0] != "list" || columns[1] != "items" { return fmt.Errorf("invalid format of lists output") } var list string var items sql.RawBytes for rows.Next() { if err = rows.Scan(&list, &items); err != nil { return fmt.Errorf("error scanning lists row: %w", err) } value, err := strconv.ParseFloat(string(items), 64) if err != nil { return fmt.Errorf("can't parse items of %q: %w", string(items), err) } if description, ok := listMetricNameToDescription[list]; ok { ch <- prometheus.MustNewConstMetric(description, prometheus.GaugeValue, value) } } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating lists rows: %w", 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 } return fmt.Errorf("got unexpected column %q", 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.5.1-rc8/prometheus/exporter/exporter_bench_test.go000066400000000000000000000072221517700303500243530ustar00rootroot00000000000000package 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.5.1-rc8/prometheus/exporter/exporter_test.go000066400000000000000000000131411517700303500232110ustar00rootroot00000000000000package main import ( "context" "errors" "io" "log/slog" "regexp" "strings" "testing" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/prometheus/client_golang/prometheus" ) 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 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.5.1-rc8/prometheus/exporter/go.mod000066400000000000000000000026711517700303500210670ustar00rootroot00000000000000module 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.23.2 github.com/prometheus/common v0.67.5 github.com/prometheus/exporter-toolkit v0.16.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.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/vsock v1.2.1 // 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/client_model v0.6.2 // indirect github.com/prometheus/procfs v0.16.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.49.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) odyssey-1.5.1-rc8/prometheus/exporter/go.sum000066400000000000000000000166701517700303500211200ustar00rootroot00000000000000github.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/exporter-toolkit v0.16.0 h1:xT/j7L2XKF+VJd6B4fpUw6xWabHrSmsUf6mYmFqyu0s= github.com/prometheus/exporter-toolkit v0.16.0/go.mod h1:d1EL8Z9674xQe/iWhwP2wDyCEoBPbXVeqDbqAUsgJWY= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= 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.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= odyssey-1.5.1-rc8/scripts/000077500000000000000000000000001517700303500153775ustar00rootroot00000000000000odyssey-1.5.1-rc8/scripts/centos/000077500000000000000000000000001517700303500166725ustar00rootroot00000000000000odyssey-1.5.1-rc8/scripts/centos/odyssey.spec000066400000000000000000000023121517700303500212430ustar00rootroot00000000000000Name: 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 %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.5.1-rc8/scripts/install_ci.sh000077500000000000000000000023051517700303500200570ustar00rootroot00000000000000#!/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.5.1-rc8/scripts/pgsql_benchmark.sh000077500000000000000000000044161517700303500211030ustar00rootroot00000000000000#!/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.5.1-rc8/scripts/pgsql_check.sh000077500000000000000000000032021517700303500202160ustar00rootroot00000000000000#!/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.5.1-rc8/scripts/pgsql_pgbouncer_benchmark.sh000077500000000000000000000022021517700303500231360ustar00rootroot00000000000000#!/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.5.1-rc8/scripts/systemd/000077500000000000000000000000001517700303500170675ustar00rootroot00000000000000odyssey-1.5.1-rc8/scripts/systemd/odyssey.service000066400000000000000000000007371517700303500221570ustar00rootroot00000000000000[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.5.1-rc8/scripts/systemd/odyssey@.service000066400000000000000000000005051517700303500222500ustar00rootroot00000000000000[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.5.1-rc8/scripts/update_version.sh000077500000000000000000000132321517700303500207660ustar00rootroot00000000000000#!/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.5.1-rc8/scripts/vdb-views.sql000066400000000000000000000110121517700303500200210ustar00rootroot00000000000000CREATE 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.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 ); 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.5.1-rc8/sources/000077500000000000000000000000001517700303500153735ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/CMakeLists.txt000066400000000000000000000261561517700303500201450ustar00rootroot00000000000000# 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/thread.c machinarium/lrand48.c machinarium/loop.c machinarium/clock.c machinarium/socket.c machinarium/stat.c machinarium/epoll.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/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/shutdown.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 od_memory.c affinity.c daemon.c pid.c logger.c pool.c rules.c config.c config_reader.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 misc.c io.c tdigest.c module.c attach.c multi_pool.c counter.c err_logger.c setproctitle.c debugprintf.c restart_sync.c readahead.c soft_oom.c grac_shutdown_worker.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 murmurhash.c hashmap.c address.c hba.c hba_reader.c hba_rule.c mdb_iamproxy.c external_auth.c group.c query_processing.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 (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") 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) 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} m) if(EXISTS "/etc/alpine-release") target_link_libraries(${od_binary} argp) 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/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_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_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_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/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_hashmap.c tests/odyssey/test_pstmt.c tests/odyssey/test_query_processing.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_link_libraries(${od_test_binary} ${od_libraries} ${CMAKE_THREAD_LIBS_INIT} m) if(EXISTS "/etc/alpine-release") target_link_libraries(${od_test_binary} argp) endif() if (BUILD_COMPRESSION) target_link_libraries(${od_test_binary} ${compression_libraries}) endif() odyssey-1.5.1-rc8/sources/address.c000066400000000000000000000322101517700303500171620ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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; } int od_address_hostname_validate(od_config_reader_t *reader, char *hostname) { int reti = regexec(&reader->rfc952_hostname_regex, hostname, 0, NULL, 0); if (reti == 0) { return 0; } return 1; } 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; } 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 */) { return NOT_OK_RESPONSE; } strcpy(buff, host_str); size_t result_count = od_config_reader_get_endpoints_count(buff, len); od_address_t *result = od_malloc(result_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); } *out = result; *count = result_count; 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) { assert(a->type == OD_ADDRESS_TYPE_UNIX); 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) { assert(a->type == OD_ADDRESS_TYPE_TCP); 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.5.1-rc8/sources/affinity.c000066400000000000000000000217321517700303500173550ustar00rootroot00000000000000#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; } 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); } } } 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.5.1-rc8/sources/attach.c000066400000000000000000000051471517700303500170120ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include static inline od_frontend_status_t od_attach_extended_try_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->address); 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)) { int rc = od_backend_connect(server, context, NULL, client); 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_try_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.5.1-rc8/sources/attribute.c000066400000000000000000000030711517700303500175430ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/auth.c000066400000000000000000001032601517700303500165020ustar00rootroot00000000000000 /* * 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)); 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)); 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)); 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)); 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)); 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); 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: 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; 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; 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; 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->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; 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; if (route->rule->storage_password) { password = route->rule->storage_password; } else if (client != NULL && client->password.password != NULL) { od_error( &instance->logger, "auth", NULL, server, "cannot authenticate with SCRAM secret from auth_query", route->rule->db_name, route->rule->user_name); return -1; } else if (route->rule->password) { password = route->rule->password; } 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 using password %s", 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; 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; 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)); 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.5.1-rc8/sources/auth_query.c000066400000000000000000000174441517700303500177370ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline void free_cache_value(od_hashmap_list_item_t *it) { od_auth_cache_value_t *p = it->value.data; od_free(p->passwd); } od_hashmap_t *od_auth_query_create_cache(size_t sz) { return od_hashmap_create_with_dtor(sz, free_cache_value); } static inline 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; } int od_auth_query(od_client_t *client, char *peer) { od_global_t *global = client->global; od_rule_t *rule = client->rule; od_rule_storage_t *storage = rule->storage; kiwi_var_t *user = &client->startup.user; kiwi_password_t *password = &client->password; od_instance_t *instance = global->instance; od_router_t *router = global->router; /* check odyssey storage auh query cache before * doing any actual work */ /* username -> password cache */ od_hashmap_elt_t *value; od_hashmap_elt_t key; od_auth_cache_value_t *cache_value; od_hash_t keyhash; uint64_t current_time; key.data = user->value; key.len = user->value_len; keyhash = od_murmur_hash(key.data, key.len); /* acquire hash map entry lock */ value = od_hashmap_lock_key(storage->acache, keyhash, &key); if (value->data == NULL) { /* one-time initialize */ value->len = sizeof(od_auth_cache_value_t); value->data = od_malloc(value->len); if (value->data == NULL) { goto error; } memset(((od_auth_cache_value_t *)(value->data)), 0, value->len); } cache_value = (od_auth_cache_value_t *)value->data; current_time = machine_time_us(); if (/* password cached for 10 sec */ current_time - cache_value->timestamp < 10 * interval_usec) { od_debug(&instance->logger, "auth_query", NULL, NULL, "reusing cached password for user %.*s", user->value_len, user->value); /* unlock hashmap entry */ password->password_len = cache_value->passwd_len; if (cache_value->passwd_len > 0) { /* */ password->password = od_malloc(password->password_len + 1); if (password->password == NULL) { goto error; } strncpy(password->password, cache_value->passwd, cache_value->passwd_len); password->password[password->password_len] = '\0'; } od_hashmap_unlock_key(storage->acache, keyhash, &key); return OK_RESPONSE; } /* create internal auth client */ od_client_t *auth_client; auth_client = od_client_allocate_internal(global, "auth-query"); if (auth_client == NULL) { od_debug(&instance->logger, "auth_query", auth_client, NULL, "failed to allocate internal auth query client"); goto error; } od_debug(&instance->logger, "auth_query", auth_client, NULL, "acquiring password for user %.*s", user->value_len, user->value); /* set auth query route user and database */ kiwi_var_set(&auth_client->startup.user, KIWI_VAR_UNDEF, rule->auth_query_user, strlen(rule->auth_query_user) + 1); kiwi_var_set(&auth_client->startup.database, KIWI_VAR_UNDEF, rule->auth_query_db, strlen(rule->auth_query_db) + 1); /* set io from client */ od_io_t auth_client_io = auth_client->io; auth_client->io = client->io; /* route */ od_router_status_t status; status = od_router_route(router, auth_client); /* return io auth_client back */ auth_client->io = auth_client_io; if (status != OD_ROUTER_OK) { od_debug(&instance->logger, "auth_query", auth_client, NULL, "failed to route internal auth query client: %s", od_router_status_to_str(status)); od_client_free(auth_client); goto error; } int rc; rc = od_attach_extended(instance, "auth_query", router, auth_client); if (rc != OK_RESPONSE) { od_router_unroute(router, auth_client); od_client_free_extended(auth_client); goto error; } od_server_t *server; server = auth_client->server; /* preformat and execute query */ char query[OD_QRY_MAX_SZ]; 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, query, sizeof(query)); machine_msg_t *msg; msg = od_query_do(server, "auth_query", query, user->value); if (msg == NULL) { od_log(&instance->logger, "auth_query", auth_client, server, "auth query returned empty msg"); od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free_extended(auth_client); goto error; } rc = od_auth_parse_passwd_from_datarow(&instance->logger, msg, password); machine_msg_free(msg); if (rc == NOT_OK_RESPONSE) { od_debug(&instance->logger, "auth_query", auth_client, server, "auth query returned datarow in incompatible format"); od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free_extended(auth_client); goto error; } /* save received password and receive timestamp */ if (cache_value->passwd != NULL) { /* drop previous value */ od_free(cache_value->passwd); /* there should be cache_value->passwd = NULL for sanity * but this is meaninigless since we assign new value just below */ } cache_value->passwd_len = password->password_len; cache_value->passwd = od_malloc(password->password_len); if (cache_value->passwd == NULL) { od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free_extended(auth_client); goto error; } strncpy(cache_value->passwd, password->password, cache_value->passwd_len); cache_value->timestamp = current_time; /* detach and unroute */ od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free_extended(auth_client); od_hashmap_unlock_key(storage->acache, keyhash, &key); return OK_RESPONSE; error: /* unlock hashmap entry */ od_hashmap_unlock_key(storage->acache, keyhash, &key); return NOT_OK_RESPONSE; } odyssey-1.5.1-rc8/sources/backend.c000066400000000000000000000511051517700303500171300ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void od_backend_close(od_server_t *server) { assert(server->route == NULL); assert(server->io.io == NULL); 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) { 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; } 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; } 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 od_backend_startup(server, route_params, client); } return 0; } 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 (route->id.physical_rep) { argv[argc + 1].name = "on"; argv[argc + 1].len = 3; argc += 2; } else if (route->id.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); assert(server->client); for (;;) { msg = od_read(&server->io, UINT32_MAX); 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; } int od_backend_connect_to(od_server_t *server, char *context, const od_address_t *address, od_tls_opts_t *tlsopts) { od_instance_t *instance = server->global->instance; assert(server->io.io == NULL); 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 */ if (tlsopts->tls_mode != OD_CONFIG_TLS_DISABLE) { server->tls = od_tls_backend(tlsopts); if (server->tls == NULL) { return -1; } } uint64_t time_connect_start = 0; if (instance->config.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; struct addrinfo *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 = machine_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; } 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 (instance->config.log_session) { time_resolve = machine_time_us() - time_connect_start; } /* connect to server */ rc = mm_io_connect( server->io.io, saddr, (uint32_t)instance->config.backend_connect_timeout_ms); if (ai) { freeaddrinfo(ai); } 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 (tlsopts->tls_mode != OD_CONFIG_TLS_DISABLE) { rc = od_tls_backend_connect(server, &instance->logger, tlsopts); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } } uint64_t time_connect = 0; if (instance->config.log_session) { time_connect = machine_time_us() - time_connect_start; } /* log server connection */ if (instance->config.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 od_retcode_t od_backend_update_endpoint_status(od_instance_t *instance, od_client_t *client, od_server_t *server, char *context, od_storage_endpoint_t *endpoint) { od_storage_endpoint_status_t status; od_storage_endpoint_status_init(&status); machine_msg_t *msg; msg = od_query_do(server, context, "SELECT pg_is_in_recovery()", NULL); 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); char addr[256]; od_address_to_str(&endpoint->address, addr, sizeof(addr) - 1); 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; } int od_backend_check_tsa(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 OK_RESPONSE; } od_global_t *global = server->global; od_instance_t *instance = global->instance; od_rule_storage_t *storage = client->rule->storage; 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, endpoint) != OK_RESPONSE) { return NOT_OK_RESPONSE; } } 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 NOT_OK_RESPONSE; } return OK_RESPONSE; } 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); if (rc == NOT_OK_RESPONSE) { return rc; } /* send startup and do initial configuration */ rc = od_backend_startup(server, 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_route_t *route = server->route; assert(route != NULL); od_rule_storage_t *storage; storage = route->rule->storage; 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; /* connect to server */ int rc; rc = od_backend_connect_to(server, "cancel", address, storage->tls_opts); 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_write(&server->io, msg); 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 * * 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 5000). * 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; machine_msg_t *unused = od_read(&server->io, cancel_timeout); 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); 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, char *query, char *param, int len) { 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_write(&server->io, msg); 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); 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) == 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.5.1-rc8/sources/cancel.c000066400000000000000000000014641517700303500167710ustar00rootroot00000000000000 /* * 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, 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.5.1-rc8/sources/client.c000066400000000000000000000037241517700303500170230ustar00rootroot00000000000000/* * 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->config_listen = NULL; client->server = NULL; client->route = NULL; client->global = NULL; client->time_accept = 0; client->time_setup = 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; 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->last_catchup_lag = 0; od_atomic_u64_set(&client->killed, 0); od_relay_init(&client->relay, client); memset(client->peer, 0, sizeof(client->peer)); } void od_client_free(od_client_t *client) { #ifdef LDAP_FOUND od_free(client->ldap_auth_dn); #endif 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->external_id) { od_free(client->external_id); } od_free(client); } uint32_t od_client_login_timeout(const od_client_t *client) { if (client->config_listen != NULL) { return (uint32_t)client->config_listen->client_login_timeout; } /* TODO: do not use infinite timeout */ return UINT32_MAX; } odyssey-1.5.1-rc8/sources/common/000077500000000000000000000000001517700303500166635ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/common/base64.c000066400000000000000000000113231517700303500201130ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/common/cryptohash_openssl.c000066400000000000000000000205371517700303500227650ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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) malloc(size) #define FREE(ptr) 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.5.1-rc8/sources/common/fe_memutils.c000066400000000000000000000053231517700303500213430ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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 = 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 = 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 = strdup(in); if (!tmp) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } return tmp; } void pg_free(void *ptr) { 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 = 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.5.1-rc8/sources/common/hmac.c000066400000000000000000000155701517700303500177470ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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) malloc(size) #define FREE(ptr) 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.5.1-rc8/sources/common/md5.c000066400000000000000000000262101517700303500175150ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/common/saslprep.c000066400000000000000000000625441517700303500206730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * 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) strdup(s) #define ALLOC(size) malloc(size) #define FREE(size) 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.5.1-rc8/sources/common/scram-common.c000066400000000000000000000174541517700303500214350ustar00rootroot00000000000000/*------------------------------------------------------------------------- * 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 = malloc(maxlen); if (!result) { *errstr = _("out of memory"); return NULL; } #else result = palloc(maxlen); #endif p = result + sprintf(result, "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 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 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 free(result); return NULL; #else elog(ERROR, "%s", *errstr); #endif } p += encoded_result; *(p++) = '\0'; Assert(p - result <= maxlen); return result; } odyssey-1.5.1-rc8/sources/common/string.c000066400000000000000000000071501517700303500203400ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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 = 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.5.1-rc8/sources/common/unicode_norm.c000066400000000000000000000367041517700303500215220ustar00rootroot00000000000000/*------------------------------------------------------------------------- * 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) malloc(size) #define FREE(size) 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.5.1-rc8/sources/common/wchar.c000066400000000000000000001423221517700303500201370ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/compression.c000066400000000000000000000030771517700303500201070ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/config.c000066400000000000000000000327461517700303500170200ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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->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->server_login_retry = 1; config->cache_coroutine = 256; config->cache_msg_gc_size = 0; config->coroutine_stack_size = 4; 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 = 5U * 1000U; /* 5 seconds */ 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; config->host_watcher_enabled = 0; config->smart_search_path_enquoting = 0; } void od_config_reload(od_config_t *current_config, od_config_t *new_config) { current_config->log_debug = new_config->log_debug; 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->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->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->target_session_attrs = OD_TARGET_SESSION_ATTRS_UNDEF; 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_free(config); } int od_config_validate(od_config_t *config, od_logger_t *logger) { /* workers */ if (config->workers <= 0) { od_error(logger, "config", NULL, NULL, "bad workers number"); return -1; } /* resolvers */ if (config->resolvers <= 0) { od_error(logger, "config", NULL, NULL, "bad resolvers number"); return -1; } /* coroutine_stack_size */ if (config->coroutine_stack_size < 4) { od_error(logger, "config", NULL, NULL, "bad coroutine_stack_size number"); return -1; } /* log format */ if (config->log_format == NULL) { od_error(logger, "config", NULL, NULL, "log 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, "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; } } } if (config->enable_online_restart_feature && !config->bindwith_reuseport) { od_dbg_printf_on_dvl_lvl(1, "validation error detected %s\n", ""); 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"; } 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, "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, "keepalive %d", config->keepalive); 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_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, "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, "enable_host_watcher. %d", config->host_watcher_enabled); if (config->enable_online_restart_feature) { od_log(logger, "config", NULL, NULL, "online restart enabled: OK"); } if (config->graceful_die_on_errors) { od_log(logger, "config", NULL, NULL, "graceful die enabled: OK"); } if (config->bindwith_reuseport) { od_log(logger, "config", NULL, NULL, "socket bind with: SO_REUSEPORT"); } 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); } else { od_log(logger, "config", NULL, NULL, "soft_oom: DISABLED"); } od_log(logger, "config", NULL, NULL, "SCRAM auth method: OK"); od_log(logger, "config", NULL, NULL, ""); 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); 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); } od_log(logger, "config", NULL, NULL, ""); } } odyssey-1.5.1-rc8/sources/config_reader.c000066400000000000000000002647161517700303500203460ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PROMHTTP_FOUND #include #include #endif typedef enum { OD_LYES, OD_LNO, OD_LINCLUDE, OD_LDAEMONIZE, OD_LPRIORITY, OD_LSEQROUTING, OD_LLOG_TO_STDOUT, OD_LLOG_DEBUG, OD_LLOG_CONFIG, OD_LLOG_SESSION, OD_LLOG_QUERY, OD_LLOG_FILE, OD_LLOG_FORMAT, OD_LLOG_STATS, OD_LLOG_ASYNC, OD_LLOG_QUEUE_DEPTH, /* Prometheus */ OD_LLOG_GENERAL_STATS_PROM, OD_LLOG_ROUTE_STATS_PROM, OD_LPROMHTTP_PORT, OD_LPID_FILE, OD_LUNIX_SOCKET_DIR, OD_LUNIX_SOCKET_MODE, OD_LLOCKS_DIR, OD_LENABLE_ONLINE_RESTART, OD_LAVAILABILITY_ZONE, OD_LCONN_DROP_OPTIONS, OD_LONLINE_RESTART_DROP_OPTIONS, OD_LONLINE_RESTART_DROP_ENABLED, OD_LINTERVAL_MS, OD_LRATE, OD_LGRACEFUL_DIE_ON_ERRORS, OD_LGRACEFUL_SHUTDOWN_TIMEOUT_MS, OD_LBINDWITH_REUSEPORT, OD_LLOG_SYSLOG, OD_LLOG_SYSLOG_IDENT, OD_LLOG_SYSLOG_FACILITY, OD_LSTATS_INTERVAL, OD_LLISTEN, OD_LHOST, OD_LPORT, OD_LTARGET_SESSION_ATTRS, OD_LBACKLOG, OD_LNODELAY, OD_LDISABLE_NOLINGER, OD_LKEEPALIVE, OD_LKEEPALIVE_INTERVAL, OD_LKEEPALIVE_PROBES, OD_LKEEPALIVE_USR_TIMEOUT, OD_LREADAHEAD, OD_LWORKERS, OD_LCPU_AFFINITY, OD_LRESOLVERS, OD_LPIPELINE, OD_LSMART_SEARCH_PATH_ENQUOTING, OD_LPACKET_READ_SIZE, OD_LPACKET_WRITE_QUEUE, OD_LCACHE, OD_LCACHE_CHUNK, OD_LCACHE_MSG_GC_SIZE, OD_LCACHE_COROUTINE, OD_LCOROUTINE_STACK_SIZE, OD_LCLIENT_MAX, OD_LCLIENT_MAX_ROUTING, OD_LMAX_SIGTERMS_TO_DIE, OD_LEXTERNAL_AUTH_SOCKET_PATH, OD_LENABLE_HOST_WATCHER, OD_LSERVER_LOGIN_RETRY, OD_LCLIENT_LOGIN_TIMEOUT, OD_LCLIENT_FWD_ERROR, OD_LPRESERVE_SESSION_SERVER_CONN, OD_LAPPLICATION_NAME_ADD_HOST, OD_LBACKEND_CONNECT_TIMEOUT_MS, OD_LCANCEL_TIMEOUT_MS, OD_LSERVER_LIFETIME, OD_LSERVER_DROP_ON_BACKED_PLAN_ERROR, OD_LTLS, OD_LTLS_CA_FILE, OD_LTLS_KEY_FILE, OD_LTLS_CERT_FILE, OD_LTLS_PROTOCOLS, OD_LCOMPRESSION, OD_LSTORAGE, OD_LENDPOINTS_STATUS_POLL_INTERVAL, OD_LTYPE, OD_LSERVERS_MAX_ROUTING, OD_LDEFAULT, OD_LDATABASE, OD_LUSER, OD_LGROUP, OD_LPASSWORD, OD_LROLE, OD_LPOOL, OD_LPOOL_ROUTING, #ifdef LDAP_FOUND OD_LLDAPPOOL_SIZE, OD_LLDAPPOOL_TIMEOUT, OD_LLDAPPOOL_TTL, #endif OD_LPOOL_MIN_SIZE, OD_LPOOL_SIZE, OD_LPOOL_TIMEOUT, OD_LPOOL_TTL, OD_LPOOL_DISCARD, OD_LPOOL_SMART_DISCARD, OD_LPOOL_DISCARD_QUERY, OD_LPOOL_CANCEL, OD_LPOOL_ROLLBACK, OD_LPOOL_RESET_TIMEOUT_MS, OD_LPOOL_RESERVE_PREPARED_STATEMENT, OD_LPOOL_CLIENT_IDLE_TIMEOUT, OD_LPOOL_IDLE_IN_TRANSACTION_TIMEOUT, OD_LPOOL_PIN_ON_LISTEN, OD_LPOOL_NOTICE_AFTER_WAITING_MS, OD_LPOOL_ATTACH_CHECK, OD_LSHARED_POOL, OD_LSTORAGE_DB, OD_LSTORAGE_USER, OD_LSTORAGE_PASSWORD, OD_LAUTHENTICATION, OD_LAUTH_COMMON_NAME, OD_LAUTH_PAM_SERVICE, OD_LAUTH_MODULE, OD_LAUTH_QUERY, OD_LAUTH_QUERY_DB, OD_LAUTH_QUERY_USER, OD_LAUTH_LDAP_SERVICE, OD_LAUTH_PASSWORD_PASSTHROUGH, OD_LAUTH_MDB_IAMPROXY_ENABLE, OD_LAUTH_MDB_IAMPROXY_SOCKET_PATH, OD_LQUANTILES, OD_LMODULE, OD_LMAINRAIN_PARAMS, OD_LLDAP_ENDPOINT, OD_LLDAP_SERVER, OD_LLDAP_PORT, OD_LLDAP_PREFIX, OD_LLDAP_SUFFIX, OD_LLDAP_BASEDN, OD_LLDAP_BINDDN, OD_LLDAP_URL, OD_LLDAP_SEARCH_ATTRIBUTE, OD_LLDAP_BIND_PASSWD, OD_LLDAP_SCHEME, OD_LLDAP_SCOPE, OD_LLDAP_SEARCH_FILTER, OD_LLDAP_ENDPOINT_NAME, OD_LLDAP_STORAGE_CREDENTIALS_ATTR, OD_LLDAP_STORAGE_CREDENTIALS, OD_LLDAP_STORAGE_USERNAME, OD_LLDAP_STORAGE_PASSWORD, OD_LWATCHDOG, OD_LWATCHDOG_LAG_QUERY, OD_LWATCHDOG_LAG_INTERVAL, OD_LCATCHUP_TIMEOUT, OD_LCATCHUP_CHECKS, OD_LOPTIONS, OD_LBACKEND_STARTUP_OPTIONS, OD_LHBA_FILE, OD_LGROUP_QUERY, OD_LGROUP_QUERY_USER, OD_LGROUP_QUERY_DB, OD_LGROUP_CHECKER_INTERVAL, OD_LVIRTUAL_PROC, OD_LSOFT_OOM, OD_LLIMIT, OD_LPROCESS, OD_LCHECK_INTERVAL_MS, OD_LDROP, OD_LSIGNAL, OD_LMAX_RATE, OD_LLOCAL, OD_LHOSTSSL, OD_LHOSTNOSSL, } od_lexeme_t; static od_keyword_t od_config_keywords[] = { /* main */ od_keyword("yes", OD_LYES), od_keyword("no", OD_LNO), od_keyword("include", OD_LINCLUDE), od_keyword("daemonize", OD_LDAEMONIZE), od_keyword("priority", OD_LPRIORITY), od_keyword("sequential_routing", OD_LSEQROUTING), od_keyword("pid_file", OD_LPID_FILE), od_keyword("unix_socket_dir", OD_LUNIX_SOCKET_DIR), od_keyword("unix_socket_mode", OD_LUNIX_SOCKET_MODE), od_keyword("locks_dir", OD_LLOCKS_DIR), od_keyword("external_auth_socket_path", OD_LEXTERNAL_AUTH_SOCKET_PATH), od_keyword("enable_online_restart", OD_LENABLE_ONLINE_RESTART), od_keyword("availability_zone", OD_LAVAILABILITY_ZONE), od_keyword("graceful_die_on_errors", OD_LGRACEFUL_DIE_ON_ERRORS), od_keyword("graceful_shutdown_timeout_ms", OD_LGRACEFUL_SHUTDOWN_TIMEOUT_MS), od_keyword("bindwith_reuseport", OD_LBINDWITH_REUSEPORT), od_keyword("conn_drop_options", OD_LCONN_DROP_OPTIONS), od_keyword("online_restart_drop_options", OD_LONLINE_RESTART_DROP_OPTIONS), od_keyword("drop_enabled", OD_LONLINE_RESTART_DROP_ENABLED), od_keyword("rate", OD_LRATE), od_keyword("interval_ms", OD_LINTERVAL_MS), od_keyword("enable_host_watcher", OD_LENABLE_HOST_WATCHER), /* logging */ od_keyword("log_debug", OD_LLOG_DEBUG), od_keyword("log_to_stdout", OD_LLOG_TO_STDOUT), od_keyword("log_config", OD_LLOG_CONFIG), od_keyword("log_session", OD_LLOG_SESSION), od_keyword("log_query", OD_LLOG_QUERY), od_keyword("log_file", OD_LLOG_FILE), od_keyword("log_format", OD_LLOG_FORMAT), od_keyword("log_stats", OD_LLOG_STATS), od_keyword("log_async", OD_LLOG_ASYNC), od_keyword("log_queue_depth", OD_LLOG_QUEUE_DEPTH), od_keyword("log_syslog", OD_LLOG_SYSLOG), od_keyword("log_syslog_ident", OD_LLOG_SYSLOG_IDENT), od_keyword("log_syslog_facility", OD_LLOG_SYSLOG_FACILITY), od_keyword("stats_interval", OD_LSTATS_INTERVAL), od_keyword("smart_search_path_enquoting", OD_LSMART_SEARCH_PATH_ENQUOTING), /* Prometheus */ od_keyword("log_general_stats_prom", OD_LLOG_GENERAL_STATS_PROM), od_keyword("log_route_stats_prom", OD_LLOG_ROUTE_STATS_PROM), od_keyword("promhttp_server_port", OD_LPROMHTTP_PORT), /* listen */ od_keyword("listen", OD_LLISTEN), od_keyword("host", OD_LHOST), od_keyword("port", OD_LPORT), /* target_session_attrs */ od_keyword("target_session_attrs", OD_LTARGET_SESSION_ATTRS), od_keyword("backlog", OD_LBACKLOG), od_keyword("nodelay", OD_LNODELAY), od_keyword("disable_nolinger", OD_LDISABLE_NOLINGER), /* TCP keepalive */ od_keyword("keepalive", OD_LKEEPALIVE), od_keyword("keepalive_keep_interval", OD_LKEEPALIVE_INTERVAL), od_keyword("keepalive_probes", OD_LKEEPALIVE_PROBES), od_keyword("keepalive_usr_timeout", OD_LKEEPALIVE_USR_TIMEOUT), /* */ od_keyword("max_sigterms_to_die", OD_LMAX_SIGTERMS_TO_DIE), od_keyword("readahead", OD_LREADAHEAD), od_keyword("workers", OD_LWORKERS), od_keyword("cpu_affinity", OD_LCPU_AFFINITY), od_keyword("resolvers", OD_LRESOLVERS), od_keyword("pipeline", OD_LPIPELINE), od_keyword("packet_read_size", OD_LPACKET_READ_SIZE), od_keyword("packet_write_queue", OD_LPACKET_WRITE_QUEUE), od_keyword("cache", OD_LCACHE), od_keyword("cache_chunk", OD_LCACHE_CHUNK), od_keyword("cache_msg_gc_size", OD_LCACHE_MSG_GC_SIZE), od_keyword("cache_coroutine", OD_LCACHE_COROUTINE), od_keyword("coroutine_stack_size", OD_LCOROUTINE_STACK_SIZE), /* client */ od_keyword("client_max", OD_LCLIENT_MAX), od_keyword("client_max_routing", OD_LCLIENT_MAX_ROUTING), od_keyword("server_login_retry", OD_LSERVER_LOGIN_RETRY), od_keyword("client_login_timeout", OD_LCLIENT_LOGIN_TIMEOUT), od_keyword("client_fwd_error", OD_LCLIENT_FWD_ERROR), od_keyword("reserve_session_server_connection", OD_LPRESERVE_SESSION_SERVER_CONN), od_keyword("application_name_add_host", OD_LAPPLICATION_NAME_ADD_HOST), od_keyword("server_lifetime", OD_LSERVER_LIFETIME), od_keyword("server_drop_on_cached_plan_error", OD_LSERVER_DROP_ON_BACKED_PLAN_ERROR), od_keyword("backend_connect_timeout_ms", OD_LBACKEND_CONNECT_TIMEOUT_MS), od_keyword("cancel_timeout_ms", OD_LCANCEL_TIMEOUT_MS), /* tls */ od_keyword("tls", OD_LTLS), od_keyword("tls_ca_file", OD_LTLS_CA_FILE), od_keyword("tls_key_file", OD_LTLS_KEY_FILE), od_keyword("tls_cert_file", OD_LTLS_CERT_FILE), od_keyword("tls_protocols", OD_LTLS_PROTOCOLS), od_keyword("compression", OD_LCOMPRESSION), /* storage */ od_keyword("storage", OD_LSTORAGE), od_keyword("endpoints_status_poll_interval", OD_LENDPOINTS_STATUS_POLL_INTERVAL), od_keyword("type", OD_LTYPE), od_keyword("server_max_routing", OD_LSERVERS_MAX_ROUTING), od_keyword("default", OD_LDEFAULT), /* database */ od_keyword("database", OD_LDATABASE), od_keyword("group", OD_LGROUP), od_keyword("user", OD_LUSER), od_keyword("password", OD_LPASSWORD), od_keyword("role", OD_LROLE), od_keyword("pool", OD_LPOOL), od_keyword("pool_routing", OD_LPOOL_ROUTING), #ifdef LDAP_FOUND od_keyword("ldap_pool_size", OD_LLDAPPOOL_SIZE), od_keyword("ldap_pool_timeout", OD_LLDAPPOOL_TIMEOUT), od_keyword("ldap_pool_ttl", OD_LLDAPPOOL_TTL), #endif od_keyword("min_pool_size", OD_LPOOL_MIN_SIZE), od_keyword("pool_size", OD_LPOOL_SIZE), od_keyword("pool_timeout", OD_LPOOL_TIMEOUT), od_keyword("pool_ttl", OD_LPOOL_TTL), od_keyword("pool_discard", OD_LPOOL_DISCARD), od_keyword("pool_discard_query", OD_LPOOL_DISCARD_QUERY), od_keyword("pool_smart_discard", OD_LPOOL_SMART_DISCARD), od_keyword("pool_cancel", OD_LPOOL_CANCEL), od_keyword("pool_rollback", OD_LPOOL_ROLLBACK), od_keyword("pool_reset_timeout_ms", OD_LPOOL_RESET_TIMEOUT_MS), od_keyword("pool_reserve_prepared_statement", OD_LPOOL_RESERVE_PREPARED_STATEMENT), od_keyword("pool_client_idle_timeout", OD_LPOOL_CLIENT_IDLE_TIMEOUT), od_keyword("pool_idle_in_transaction_timeout", OD_LPOOL_IDLE_IN_TRANSACTION_TIMEOUT), od_keyword("pool_pin_on_listen", OD_LPOOL_PIN_ON_LISTEN), od_keyword("pool_notice_after_waiting_ms", OD_LPOOL_NOTICE_AFTER_WAITING_MS), od_keyword("pool_attach_check", OD_LPOOL_ATTACH_CHECK), od_keyword("shared_pool", OD_LSHARED_POOL), od_keyword("storage_db", OD_LSTORAGE_DB), od_keyword("storage_user", OD_LSTORAGE_USER), od_keyword("storage_password", OD_LSTORAGE_PASSWORD), /* group */ od_keyword("group_query", OD_LGROUP_QUERY), od_keyword("group_query_user", OD_LGROUP_QUERY_USER), od_keyword("group_query_db", OD_LGROUP_QUERY_DB), od_keyword("group_checker_interval", OD_LGROUP_CHECKER_INTERVAL), /* auth */ od_keyword("authentication", OD_LAUTHENTICATION), od_keyword("auth_common_name", OD_LAUTH_COMMON_NAME), od_keyword("auth_query", OD_LAUTH_QUERY), od_keyword("auth_query_db", OD_LAUTH_QUERY_DB), od_keyword("auth_query_user", OD_LAUTH_QUERY_USER), od_keyword("auth_pam_service", OD_LAUTH_PAM_SERVICE), od_keyword("auth_module", OD_LAUTH_MODULE), od_keyword("password_passthrough", OD_LAUTH_PASSWORD_PASSTHROUGH), od_keyword("load_module", OD_LMODULE), od_keyword("hba_file", OD_LHBA_FILE), od_keyword("enable_mdb_iamproxy_auth", OD_LAUTH_MDB_IAMPROXY_ENABLE), od_keyword("mdb_iamproxy_socket_path", OD_LAUTH_MDB_IAMPROXY_SOCKET_PATH), /* ldap */ od_keyword("ldap_endpoint", OD_LLDAP_ENDPOINT), od_keyword("ldapserver", OD_LLDAP_SERVER), od_keyword("ldapport", OD_LLDAP_PORT), od_keyword("ldapprefix", OD_LLDAP_PREFIX), od_keyword("ldapsuffix", OD_LLDAP_SUFFIX), od_keyword("ldapbasedn", OD_LLDAP_BASEDN), od_keyword("ldapbinddn", OD_LLDAP_BINDDN), od_keyword("ldapbindpasswd", OD_LLDAP_BIND_PASSWD), od_keyword("ldapurl", OD_LLDAP_URL), od_keyword("ldapsearchattribute", OD_LLDAP_SEARCH_ATTRIBUTE), od_keyword("ldapscheme", OD_LLDAP_SCHEME), od_keyword("ldapsearchfilter", OD_LLDAP_SEARCH_FILTER), od_keyword("ldapscope", OD_LLDAP_SCOPE), od_keyword("ldap_endpoint_name", OD_LLDAP_ENDPOINT_NAME), od_keyword("ldap_storage_credentials_attr", OD_LLDAP_STORAGE_CREDENTIALS_ATTR), od_keyword("ldap_storage_credentials", OD_LLDAP_STORAGE_CREDENTIALS), od_keyword("ldap_storage_username", OD_LLDAP_STORAGE_USERNAME), od_keyword("ldap_storage_password", OD_LLDAP_STORAGE_PASSWORD), /* watchdog */ od_keyword("watchdog", OD_LWATCHDOG), od_keyword("watchdog_lag_query", OD_LWATCHDOG_LAG_QUERY), od_keyword("watchdog_lag_interval", OD_LWATCHDOG_LAG_INTERVAL), od_keyword("catchup_timeout", OD_LCATCHUP_TIMEOUT), od_keyword("catchup_checks", OD_LCATCHUP_CHECKS), /* options */ od_keyword("options", OD_LOPTIONS), od_keyword("maintain_params", OD_LMAINRAIN_PARAMS), od_keyword("backend_startup_options", OD_LBACKEND_STARTUP_OPTIONS), /* full virtual processing */ od_keyword("virtual_processing", OD_LVIRTUAL_PROC), /* stats */ od_keyword("quantiles", OD_LQUANTILES), /* soft_oom */ od_keyword("soft_oom", OD_LSOFT_OOM), od_keyword("limit", OD_LLIMIT), od_keyword("process", OD_LPROCESS), od_keyword("check_interval_ms", OD_LCHECK_INTERVAL_MS), od_keyword("drop", OD_LDROP), od_keyword("signal", OD_LSIGNAL), od_keyword("max_rate", OD_LMAX_RATE), /* connection type in routes */ od_keyword("local", OD_LLOCAL), od_keyword("hostssl", OD_LHOSTSSL), od_keyword("hostnossl", OD_LHOSTNOSSL), { 0, 0, 0 }, }; static od_keyword_t od_role_keywords[] = { od_keyword("admin", OD_RULE_ROLE_ADMIN), od_keyword("stat", OD_RULE_ROLE_STAT), od_keyword("notallow", OD_RULE_ROLE_NOTALLOW), { 0, 0, 0 }, }; static inline int od_config_reader_watchdog(od_config_reader_t *reader, od_storage_watchdog_t *watchdog, od_extension_t *extensions); static int od_config_reader_open(od_config_reader_t *reader, char *config_file) { od_list_init(&reader->shared_pools); reader->config_file = config_file; /* read file */ char *config_buf = NULL; FILE *file = fopen(config_file, "re"); if (file == NULL) { goto error; } fseek(file, 0, SEEK_END); int size = (int)ftell(file); if (size == -1) { goto error; } fseek(file, 0, SEEK_SET); config_buf = od_malloc(size); if (config_buf == NULL) { goto error; } int rc = fread(config_buf, size, 1, file); if (rc != 1) { od_free(config_buf); goto error; } switch (fclose(file)) { case 0: { reader->data = config_buf; reader->data_size = size; od_parser_init(&reader->parser, reader->data, reader->data_size); return 0; } case EOF: { od_errorf(reader->error, "failed to close config file '%s': %d", config_file, errno); od_free(config_buf); return NOT_OK_RESPONSE; } default: assert(0); } error: od_errorf(reader->error, "failed to open config file '%s'", config_file); if (file) { fclose(file); } return NOT_OK_RESPONSE; } static void od_config_reader_close(od_config_reader_t *reader) { od_list_t *i, *s; od_list_foreach_safe (&reader->shared_pools, i, s) { od_shared_pool_t *sp; 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); } if (reader->data_size > 0) { od_free(reader->data); } } static bool od_config_reader_is(od_config_reader_t *reader, int id) { od_token_t token; int rc; token.line = 0; rc = od_parser_next(&reader->parser, &token); od_parser_push(&reader->parser, &token); if (rc != id) { return false; } return true; } bool od_config_reader_keyword(od_config_reader_t *reader, od_keyword_t *keyword) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_KEYWORD) { goto error; } od_keyword_t *match; match = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { goto error; } if (keyword != match) { goto error; } return true; error: od_parser_push(&reader->parser, &token); const char *kwname = "unknown"; if (keyword) { kwname = keyword->name; } od_config_reader_error(reader, &token, "expected '%s'", kwname); return false; } static bool od_config_reader_quantiles(od_config_reader_t *reader, char *value, double **quantiles, int *count) { int comma_cnt = 1; char *c = value; while (*c) { if (*c == ',') { comma_cnt++; } c++; } *quantiles = od_malloc(sizeof(double) * comma_cnt); if (*quantiles == NULL) { return false; } double *array = *quantiles; *count = 0; c = value; while (*c) { int length = sscanf(c, "%lf", array + *count); if (length != 1 || array[*count] > 1 || array[*count] < 0) { od_config_reader_error(reader, NULL, "incorrect quantile value"); od_free(*quantiles); return false; } *count += 1; while (*c != '\0' && *c != ',') { c++; } if (*c == '\0') { break; } c++; } return true; } static bool od_config_reader_string(od_config_reader_t *reader, char **value) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_STRING) { od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "expected 'string'"); return false; } char *copy = od_malloc(token.value.string.size + 1); if (copy == NULL) { od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "memory allocation error"); return false; } memcpy(copy, token.value.string.pointer, token.value.string.size); copy[token.value.string.size] = 0; if (*value) { od_free(*value); } *value = copy; return true; } static bool od_config_reader_number(od_config_reader_t *reader, int *number) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_NUM) { od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "expected 'number'"); return false; } /* uint64 to int conversion */ *number = token.value.num; return true; } static bool od_config_reader_number64(od_config_reader_t *reader, uint64_t *number) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_NUM) { od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "expected 'number'"); return false; } *number = token.value.num; return true; } static bool od_config_reader_target_session_attrs(od_config_reader_t *reader, od_target_session_attrs_t *out) { char *tmp = NULL; if (!od_config_reader_string(reader, &tmp)) { return false; } if (strcmp(tmp, "read-write") == 0) { *out = OD_TARGET_SESSION_ATTRS_RW; } else if (strcmp(tmp, "any") == 0) { *out = OD_TARGET_SESSION_ATTRS_ANY; } else if (strcmp(tmp, "read-only") == 0) { *out = OD_TARGET_SESSION_ATTRS_RO; } else { od_config_reader_error( reader, NULL, "can't parse target session attrs from '%s'", tmp); od_free(tmp); return false; } od_free(tmp); return true; } struct sig_name_num { const char *name; int num; }; int od_config_reader_sig_number_from_name(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 inline od_shared_pool_t * od_config_reader_shared_pool_find(od_config_reader_t *reader, const char *name) { od_list_t *i; od_list_foreach (&reader->shared_pools, i) { od_shared_pool_t *sp; sp = od_container_of(i, od_shared_pool_t, link); if (strcmp(sp->name, name) == 0) { return sp; } } return NULL; } static bool od_config_reader_signal(od_config_reader_t *reader, int *signum) { char *tmp = NULL; if (!od_config_reader_string(reader, &tmp)) { return false; } *signum = od_config_reader_sig_number_from_name(tmp); od_free(tmp); if (*signum == -1) { od_config_reader_error(reader, NULL, "can't parse signal from '%s'", tmp); return false; } return true; } static bool od_config_reader_yes_no(od_config_reader_t *reader, int *value) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_KEYWORD) { goto error; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { goto error; } switch (keyword->id) { case OD_LYES: *value = 1; break; case OD_LNO: *value = 0; break; default: goto error; } return true; error: od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "expected 'yes/no'"); return false; } static int od_config_reader_storage_host(od_config_reader_t *reader, od_rule_storage_t *storage) { if (!od_config_reader_string(reader, &storage->host)) { return NOT_OK_RESPONSE; } if (od_storage_parse_endpoints(storage->host, &storage->endpoints, &storage->endpoints_count) != OK_RESPONSE) { od_config_reader_error(reader, NULL, "can't parse endpoints from: %s", storage->host); return NOT_OK_RESPONSE; } for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_status_init(&storage->endpoints[i].status); } return OK_RESPONSE; } static int od_config_reader_soft_oom_drop(od_config_reader_t *reader, od_config_soft_oom_drop_t *drop) { if (drop->enabled) { od_config_reader_error(reader, NULL, "drop options in soft oom is redefined"); return NOT_OK_RESPONSE; } if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { drop->enabled = 1; return OK_RESPONSE; } /* fall through */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter of type %d", rc); return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } switch (keyword->id) { /* signal */ case OD_LSIGNAL: if (!od_config_reader_signal(reader, &drop->signal)) { return NOT_OK_RESPONSE; } continue; /* max_rate */ case OD_LMAX_RATE: if (!od_config_reader_number(reader, &drop->max_rate)) { return NOT_OK_RESPONSE; } continue; default: od_config_reader_error(reader, &token, "unexpected keyword"); return NOT_OK_RESPONSE; } } od_unreachable(); return NOT_OK_RESPONSE; } static int od_config_reader_soft_oom_options(od_config_reader_t *reader) { od_config_t *config = reader->config; od_config_soft_oom_t *soft_oom = &config->soft_oom; if (soft_oom->enabled) { od_config_reader_error(reader, NULL, "soft oom is redefined"); return NOT_OK_RESPONSE; } if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { if (soft_oom->limit_bytes == 0) { od_config_reader_error( reader, &token, "limit is not set in soft_oom"); return NOT_OK_RESPONSE; } soft_oom->enabled = 1; return OK_RESPONSE; } /* fall through */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter of type %d", rc); return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } switch (keyword->id) { /* limit */ case OD_LLIMIT: if (!od_config_reader_number64( reader, &soft_oom->limit_bytes)) { return NOT_OK_RESPONSE; } continue; /* process */ case OD_LPROCESS: { char *value = NULL; if (!od_config_reader_string(reader, &value)) { return NOT_OK_RESPONSE; } if (strlen(value) >= sizeof(soft_oom->process)) { od_config_reader_error( reader, &token, "too long process name, max is %d", sizeof(soft_oom->process)); return NOT_OK_RESPONSE; } strcpy(soft_oom->process, value); od_free(value); continue; } /* check_interval_ms */ case OD_LCHECK_INTERVAL_MS: if (!od_config_reader_number( reader, &soft_oom->check_interval_ms)) { return NOT_OK_RESPONSE; } continue; /* drop */ case OD_LDROP: { int rc = od_config_reader_soft_oom_drop( reader, &soft_oom->drop); if (rc != OK_RESPONSE) { return rc; } continue; } default: od_config_reader_error(reader, &token, "unexpected keyword"); return NOT_OK_RESPONSE; } } od_unreachable(); return NOT_OK_RESPONSE; } static int od_config_reader_conn_drop_options(od_config_reader_t *reader) { od_config_t *config = reader->config; od_config_conn_drop_options_t *opts = &config->conn_drop_options; if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } while (1) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { return OK_RESPONSE; } /* fall through */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter of type %d", rc); return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } switch (keyword->id) { /* limit */ case OD_LONLINE_RESTART_DROP_ENABLED: if (!od_config_reader_yes_no(reader, &opts->drop_enabled)) { return NOT_OK_RESPONSE; } continue; /* rate */ case OD_LRATE: if (!od_config_reader_number(reader, &opts->rate)) { return NOT_OK_RESPONSE; } continue; /* interval_ms */ case OD_LINTERVAL_MS: if (!od_config_reader_number(reader, &opts->interval_ms)) { return NOT_OK_RESPONSE; } if (opts->interval_ms <= 0) { od_config_reader_error( reader, &token, "interval for conn drop options must be positive"); return NOT_OK_RESPONSE; } continue; default: od_config_reader_error(reader, &token, "unexpected keyword"); return NOT_OK_RESPONSE; } } od_unreachable(); return NOT_OK_RESPONSE; } static int od_config_reader_listen(od_config_reader_t *reader) { od_config_t *config = reader->config; od_config_listen_t *listen; listen = od_config_listen_add(config); if (listen == NULL) { return NOT_OK_RESPONSE; } /* { */ if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { return OK_RESPONSE; } /* fall through */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } switch (keyword->id) { /* host */ case OD_LHOST: if (!od_config_reader_string(reader, &listen->host)) { return NOT_OK_RESPONSE; } continue; /* port */ case OD_LPORT: if (!od_config_reader_number(reader, &listen->port)) { return NOT_OK_RESPONSE; } continue; /* target_session_attrs */ case OD_LTARGET_SESSION_ATTRS: if (!od_config_reader_target_session_attrs( reader, &listen->target_session_attrs)) { return NOT_OK_RESPONSE; } continue; /* client_login_timeout */ case OD_LCLIENT_LOGIN_TIMEOUT: if (!od_config_reader_number( reader, &listen->client_login_timeout)) { return NOT_OK_RESPONSE; } continue; /* backlog */ case OD_LBACKLOG: if (!od_config_reader_number(reader, &listen->backlog)) { return NOT_OK_RESPONSE; } continue; /* tls */ case OD_LTLS: if (!od_config_reader_string(reader, &listen->tls_opts->tls)) { return NOT_OK_RESPONSE; } continue; /* tls_ca_file */ case OD_LTLS_CA_FILE: if (!od_config_reader_string( reader, &listen->tls_opts->tls_ca_file)) { return NOT_OK_RESPONSE; } continue; /* tls_key_file */ case OD_LTLS_KEY_FILE: if (!od_config_reader_string( reader, &listen->tls_opts->tls_key_file)) { return NOT_OK_RESPONSE; } continue; /* tls_cert_file */ case OD_LTLS_CERT_FILE: if (!od_config_reader_string( reader, &listen->tls_opts->tls_cert_file)) { return NOT_OK_RESPONSE; } continue; /* tls_protocols */ case OD_LTLS_PROTOCOLS: if (!od_config_reader_string( reader, &listen->tls_opts->tls_protocols)) { return NOT_OK_RESPONSE; } continue; /* compression */ case OD_LCOMPRESSION: if (!od_config_reader_yes_no(reader, &listen->compression)) { return NOT_OK_RESPONSE; } continue; default: od_config_reader_error(reader, &token, "unexpected parameter"); return NOT_OK_RESPONSE; } } /* unreach */ return NOT_OK_RESPONSE; } static int od_config_reader_storage(od_config_reader_t *reader, od_extension_t *extensions) { od_rule_storage_t *storage; storage = od_rules_storage_allocate(); if (storage == NULL) { return NOT_OK_RESPONSE; } /* name */ if (!od_config_reader_string(reader, &storage->name)) { goto error; } if (od_rules_storage_match(reader->rules, storage->name) != NULL) { od_config_reader_error(reader, NULL, "duplicate storage definition: %s", storage->name); goto error; } od_rules_storage_add(reader->rules, storage); /* { */ if (!od_config_reader_symbol(reader, '{')) { goto error; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: { od_config_reader_error(reader, &token, "unexpected end of config file"); goto error; } case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { return OK_RESPONSE; } /* fall through */ default: { od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); goto error; } } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); goto error; } switch (keyword->id) { /* type */ case OD_LTYPE: if (!od_config_reader_string(reader, &storage->type)) { goto error; } continue; /* host */ case OD_LHOST: if (od_config_reader_storage_host(reader, storage) != OK_RESPONSE) { goto error; } continue; /* port */ case OD_LPORT: if (!od_config_reader_number(reader, &storage->port)) { goto error; } continue; /* tls */ case OD_LTLS: if (!od_config_reader_string(reader, &storage->tls_opts->tls)) { goto error; } continue; /* tls_ca_file */ case OD_LTLS_CA_FILE: if (!od_config_reader_string( reader, &storage->tls_opts->tls_ca_file)) { goto error; } continue; /* tls_key_file */ case OD_LTLS_KEY_FILE: if (!od_config_reader_string( reader, &storage->tls_opts->tls_key_file)) { goto error; } continue; /* tls_cert_file */ case OD_LTLS_CERT_FILE: if (!od_config_reader_string( reader, &storage->tls_opts->tls_cert_file)) { goto error; } continue; /* tls_protocols */ case OD_LTLS_PROTOCOLS: if (!od_config_reader_string( reader, &storage->tls_opts->tls_protocols)) { goto error; } continue; /* server_max_routing */ case OD_LSERVERS_MAX_ROUTING: if (!od_config_reader_number( reader, &storage->server_max_routing)) { goto error; } continue; /* watchdog */ case OD_LWATCHDOG: storage->watchdog = od_storage_watchdog_allocate(reader->global); if (storage->watchdog == NULL) { goto error; } if (od_config_reader_watchdog(reader, storage->watchdog, extensions) == NOT_OK_RESPONSE) { goto error; } storage->watchdog->storage = storage; continue; /* endpoints_status_poll_interval */ case OD_LENDPOINTS_STATUS_POLL_INTERVAL: if (!od_config_reader_number( reader, &storage->endpoints_status_poll_interval_ms)) { goto error; } if (storage->endpoints_status_poll_interval_ms <= 0) { od_config_reader_error( reader, &token, "endpoints_status_poll_interval can't be <= 0"); goto error; } continue; default: { od_config_reader_error(reader, &token, "unexpected parameter"); goto error; } } } /* unreach */ error: if (storage->watchdog) { od_storage_watchdog_free(storage->watchdog); storage->watchdog = NULL; } od_rules_storage_free(storage); return NOT_OK_RESPONSE; } static int od_config_reader_shared_pool(od_config_reader_t *reader) { char *name = NULL; od_shared_pool_t *shared_pool = NULL; if (!od_config_reader_string(reader, &name)) { goto error; } shared_pool = od_shared_pool_create(name); od_free(name); name = NULL; if (shared_pool == NULL) { goto error; } if (od_config_reader_shared_pool_find(reader, shared_pool->name) != NULL) { od_config_reader_error(reader, NULL, "duplicate shared pool definition: %s", shared_pool->name); goto error; } if (!od_config_reader_symbol(reader, '{')) { goto error; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: { od_config_reader_error(reader, &token, "unexpected end of config file"); goto error; } case OD_PARSER_SYMBOL: if (token.value.num == '}') { if (shared_pool->pool_size <= 0) { od_config_reader_error( reader, &token, "shared pool size %d makes no sense", shared_pool->pool_size); goto error; } od_list_append(&reader->shared_pools, &shared_pool->link); return OK_RESPONSE; } /* fallthrough */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); goto error; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); goto error; } switch (keyword->id) { /* pool_size */ case OD_LPOOL_SIZE: if (!od_config_reader_number(reader, &shared_pool->pool_size)) { goto error; } if (shared_pool->pool_size <= 0) { od_config_reader_error( reader, &token, "shared pool size %d makes no sense", shared_pool->pool_size); goto error; } continue; default: od_config_reader_error(reader, &token, "unexpected parameter"); goto error; } } od_unreachable(); error: od_free(name); if (shared_pool != NULL) { od_shared_pool_unref(shared_pool); } return NOT_OK_RESPONSE; } static inline int od_config_reader_pgoptions_kv_pair( od_config_reader_t *reader, od_token_t *token, char **optarg, size_t *optarg_len, char **optval, size_t *optval_len) { *optarg_len = token->value.string.size; *optarg = od_malloc(*optarg_len + 1); if (*optarg == NULL) { return NOT_OK_RESPONSE; } memcpy(*optarg, token->value.string.pointer, token->value.string.size); (*optarg)[*optarg_len] = 0; int rc; rc = od_parser_next(&reader->parser, token); if (rc != OD_PARSER_STRING) { od_free(*optarg); return NOT_OK_RESPONSE; } *optval_len = token->value.string.size; *optval = od_malloc(*optval_len + 1); if (*optval == NULL) { od_free(*optarg); return NOT_OK_RESPONSE; } memcpy(*optval, token->value.string.pointer, token->value.string.size); (*optval)[*optval_len] = 0; return OK_RESPONSE; } static inline int od_config_reader_pgoptions(od_config_reader_t *reader, kiwi_vars_t *dest) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* { */ if (token.value.num == '{') { break; } /* fall through */ default: od_config_reader_error(reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } char *optarg = NULL, *optval = NULL; size_t optarg_len, optval_len; for (;;) { rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_STRING: if (od_config_reader_pgoptions_kv_pair( reader, &token, &optarg, &optarg_len, &optval, &optval_len) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } kiwi_vars_update(dest, optarg, optarg_len + 1, optval, optval_len + 1); od_free(optarg); od_free(optval); break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { return 0; } /* fall through */ case OD_PARSER_KEYWORD: default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } } } static inline int od_config_reader_backend_pgoptions(od_config_reader_t *reader, od_rule_t *rule) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* { */ if (token.value.num == '{') { break; } /* fall through */ default: od_config_reader_error(reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } untyped_kiwi_var_t *ptr = NULL; rule->backend_startup_vars_sz = 0; size_t backend_startup_vars_alloc_sz = 4; rule->backend_startup_vars = od_malloc(sizeof(untyped_kiwi_var_t) * backend_startup_vars_alloc_sz); if (rule->backend_startup_vars == NULL) { /* oom */ return NOT_OK_RESPONSE; } for (;;) { rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_STRING: assert(rule->backend_startup_vars_sz <= backend_startup_vars_alloc_sz); if (rule->backend_startup_vars_sz == backend_startup_vars_alloc_sz) { backend_startup_vars_alloc_sz *= 2; rule->backend_startup_vars = od_realloc( rule->backend_startup_vars, sizeof(untyped_kiwi_var_t) * backend_startup_vars_alloc_sz); if (rule->backend_startup_vars == NULL) { /* oom */ return NOT_OK_RESPONSE; } } ptr = &rule->backend_startup_vars [rule->backend_startup_vars_sz]; if (od_config_reader_pgoptions_kv_pair( reader, &token, &ptr->name, &ptr->name_len, &ptr->value, &ptr->value_len) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } rule->backend_startup_vars_sz++; break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { return OK_RESPONSE; } /* fall through */ case OD_PARSER_KEYWORD: default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } } } #ifdef LDAP_FOUND static inline od_retcode_t od_config_reader_ldap_storage_credentials(od_config_reader_t *reader, od_rule_t *rule) { od_ldap_storage_credentials_t *lsc_current; lsc_current = od_ldap_storage_credentials_alloc(); if (!lsc_current) { goto error; } /* name */ if (!od_config_reader_string(reader, &lsc_current->name)) { goto error; } if (strlen(lsc_current->name) == 0) { od_config_reader_error( reader, NULL, "empty ldap storage credentials definition"); goto error; } if (od_ldap_storage_credentials_find(&rule->ldap_storage_creds_list, lsc_current->name) != NULL) { od_config_reader_error( reader, NULL, "duplicate ldap storage credentials definition: %s", lsc_current->name); goto error; } od_rule_ldap_storage_credentials_add(rule, lsc_current); od_ldap_storage_credentials_free(lsc_current); /* { */ if (!od_config_reader_symbol(reader, '{')) { goto error; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { if (lsc_current->lsc_username == NULL) { od_config_reader_error( reader, NULL, "lsc_username in ldap_storage_credentials '%s' is not defined", lsc_current->name); goto error; } if (lsc_current->lsc_password == NULL) { od_config_reader_error( reader, NULL, "lsc_password in ldap_storage_credentials '%s' is not defined", lsc_current->name); goto error; } return OK_RESPONSE; } /* fall through */ case OD_PARSER_KEYWORD: break; default: od_config_reader_error(reader, &token, "unexpected symbol or token"); goto error; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } switch (keyword->id) { case OD_LLDAP_STORAGE_USERNAME: { if (!od_config_reader_string( reader, &lsc_current->lsc_username)) { goto error; } if (strlen(lsc_current->lsc_username) == 0) { od_config_reader_error( reader, &token, "lsc_username in ldap_storage_credentials '%s' cannot be empty", lsc_current->name); goto error; } } break; case OD_LLDAP_STORAGE_PASSWORD: { if (!od_config_reader_string( reader, &lsc_current->lsc_password)) { goto error; } if (strlen(lsc_current->lsc_password) == 0) { od_config_reader_error( reader, &token, "lsc_password in ldap_storage_credentials '%s' cannot be empty", lsc_current->name); goto error; } } break; } } return OK_RESPONSE; error: if (lsc_current) { od_ldap_storage_credentials_free(lsc_current); } return NOT_OK_RESPONSE; } #endif static int od_config_reader_rule_settings(od_config_reader_t *reader, od_rule_t *rule, od_extension_t *extensions, od_storage_watchdog_t *watchdog) { rule->mdb_iamproxy_socket_path = NULL; for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); return NOT_OK_RESPONSE; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { if (rule->shared_pool != NULL && rule->pool->size != 0) { od_config_reader_error( reader, &token, "setting pool_size with shared pool for %s.%s makes no sense", rule->db_is_default ? "default" : rule->db_name, rule->user_is_default ? "default" : rule->user_name); return NOT_OK_RESPONSE; } return 0; } /* fall through */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_list_t *i; bool token_ok = false; od_list_foreach (&extensions->modules->link, i) { od_module_t *curr_module; curr_module = od_container_of(i, od_module_t, link); rc = curr_module->config_rule_init_cb( rule, reader, &token); if (rc == OD_MODULE_CB_OK_RETCODE) { /* * do not "break" cycle here - let every module to read * this init param */ token_ok = true; } } if (!token_ok) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } /* continue reading config */ continue; } switch (keyword->id) { /* authentication */ case OD_LAUTHENTICATION: if (!od_config_reader_string(reader, &rule->auth)) { return NOT_OK_RESPONSE; } break; /* auth_common_name */ case OD_LAUTH_COMMON_NAME: { if (od_config_reader_is(reader, OD_PARSER_KEYWORD)) { if (!od_config_reader_keyword( reader, &od_config_keywords[OD_LDEFAULT])) { return NOT_OK_RESPONSE; } rule->auth_common_name_default = 1; break; } od_rule_auth_t *auth; auth = od_rules_auth_add(rule); if (auth == NULL) { return NOT_OK_RESPONSE; } if (!od_config_reader_string(reader, &auth->common_name)) { return NOT_OK_RESPONSE; } break; } /* auth_module */ case OD_LAUTH_MODULE: if (!od_config_reader_string(reader, &rule->auth_module)) { return NOT_OK_RESPONSE; } break; /* mdb_iamproxy authentication */ case OD_LAUTH_MDB_IAMPROXY_ENABLE: { if (!od_config_reader_yes_no( reader, &rule->enable_mdb_iamproxy_auth)) { return NOT_OK_RESPONSE; } break; } case OD_LAUTH_MDB_IAMPROXY_SOCKET_PATH: { if (!od_config_reader_string( reader, &rule->mdb_iamproxy_socket_path)) { return NOT_OK_RESPONSE; } break; } #ifdef PAM_FOUND /* auth_pam_service */ case OD_LAUTH_PAM_SERVICE: if (!od_config_reader_string(reader, &rule->auth_pam_service)) { return NOT_OK_RESPONSE; } break; #endif /* target_session_attrs */ case OD_LTARGET_SESSION_ATTRS: if (!od_config_reader_target_session_attrs( reader, &rule->target_session_attrs)) { return NOT_OK_RESPONSE; } break; /* auth_query */ case OD_LAUTH_QUERY: if (!od_config_reader_string(reader, &rule->auth_query)) { return NOT_OK_RESPONSE; } break; /* auth_query_db */ case OD_LAUTH_QUERY_DB: if (!od_config_reader_string(reader, &rule->auth_query_db)) { return NOT_OK_RESPONSE; } break; /* auth_query_user */ case OD_LAUTH_QUERY_USER: if (!od_config_reader_string(reader, &rule->auth_query_user)) { return NOT_OK_RESPONSE; } break; /* auth_query_user */ case OD_LAUTH_PASSWORD_PASSTHROUGH: if (!od_config_reader_yes_no( reader, &rule->enable_password_passthrough)) { return NOT_OK_RESPONSE; } break; /* password */ case OD_LPASSWORD: if (!od_config_reader_string(reader, &rule->password)) { return NOT_OK_RESPONSE; } rule->password_len = strlen(rule->password); continue; /* role */ case OD_LROLE: { od_token_t token; int rc; od_keyword_t *keyword; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_STRING) { od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); return NOT_OK_RESPONSE; } keyword = od_keyword_match(od_role_keywords, &token); if (keyword == NULL) { od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "expected role"); return NOT_OK_RESPONSE; } rule->user_role = keyword->id; break; } /* client_max */ case OD_LCLIENT_MAX: if (!od_config_reader_number(reader, &rule->client_max)) { return NOT_OK_RESPONSE; } if (rule->client_max > 0) { rule->client_max_set = 1; } continue; /* client_fwd_error */ case OD_LCLIENT_FWD_ERROR: if (!od_config_reader_yes_no(reader, &rule->client_fwd_error)) { return NOT_OK_RESPONSE; } continue; /* reserve_session_server_connection */ case OD_LPRESERVE_SESSION_SERVER_CONN: if (!od_config_reader_yes_no( reader, &rule->reserve_session_server_connection)) { return NOT_OK_RESPONSE; } continue; /* quantiles */ case OD_LQUANTILES: { char *quantiles_str = NULL; if (!od_config_reader_string(reader, &quantiles_str)) { od_free(quantiles_str); return NOT_OK_RESPONSE; } if (!od_config_reader_quantiles( reader, quantiles_str, &rule->quantiles, &rule->quantiles_count)) { od_free(quantiles_str); return NOT_OK_RESPONSE; } od_free(quantiles_str); } break; /* application_name_add_host */ case OD_LAPPLICATION_NAME_ADD_HOST: if (!od_config_reader_yes_no( reader, &rule->application_name_add_host)) { return NOT_OK_RESPONSE; } continue; /* server_drop_on_backend_plan_error */ case OD_LSERVER_DROP_ON_BACKED_PLAN_ERROR: if (!od_config_reader_yes_no( reader, &rule->server_drop_on_cached_plan_error)) { return NOT_OK_RESPONSE; } continue; /* server_lifetime */ case OD_LSERVER_LIFETIME: { int server_lifetime; if (!od_config_reader_number(reader, &server_lifetime)) { return NOT_OK_RESPONSE; } rule->server_lifetime_us = server_lifetime * 1000000L; } continue; #ifdef LDAP_FOUND /* ldap_pool_size */ case OD_LLDAPPOOL_SIZE: if (!od_config_reader_number(reader, &rule->ldap_pool_size)) { return NOT_OK_RESPONSE; } continue; /* ldap_pool_timeout */ case OD_LLDAPPOOL_TIMEOUT: if (!od_config_reader_number( reader, &rule->ldap_pool_timeout)) { return NOT_OK_RESPONSE; } continue; /* ldap_pool_ttl */ case OD_LLDAPPOOL_TTL: if (!od_config_reader_number(reader, &rule->ldap_pool_ttl)) { return NOT_OK_RESPONSE; } continue; #endif /* maintain_params */ case OD_LMAINRAIN_PARAMS: if (!od_config_reader_yes_no(reader, &rule->maintain_params)) { return NOT_OK_RESPONSE; } continue; /* pool */ case OD_LPOOL: if (!od_config_reader_string( reader, &rule->pool->pool_type_str)) { return NOT_OK_RESPONSE; } continue; /* pool routing */ case OD_LPOOL_ROUTING: if (!od_config_reader_string( reader, &rule->pool->routing_type)) { return NOT_OK_RESPONSE; } continue; /* min_pool_size */ case OD_LPOOL_MIN_SIZE: if (!od_config_reader_number(reader, &rule->pool->min_size)) { return NOT_OK_RESPONSE; } continue; /* pool_size */ case OD_LPOOL_SIZE: if (!od_config_reader_number(reader, &rule->pool->size)) { return NOT_OK_RESPONSE; } continue; /* pool_timeout */ case OD_LPOOL_TIMEOUT: if (!od_config_reader_number(reader, &rule->pool->timeout)) { return NOT_OK_RESPONSE; } continue; /* pool_ttl */ case OD_LPOOL_TTL: if (!od_config_reader_number(reader, &rule->pool->ttl)) { return NOT_OK_RESPONSE; } continue; /* pool_discard */ case OD_LPOOL_DISCARD: if (!od_config_reader_yes_no(reader, &rule->pool->discard)) { return NOT_OK_RESPONSE; } continue; /* pool_smart_discard */ case OD_LPOOL_SMART_DISCARD: if (!od_config_reader_yes_no( reader, &rule->pool->smart_discard)) { return NOT_OK_RESPONSE; } continue; /* pool_discard_query */ case OD_LPOOL_DISCARD_QUERY: if (!od_config_reader_string( reader, &rule->pool->discard_query)) { return NOT_OK_RESPONSE; } continue; /* pool_cancel */ case OD_LPOOL_CANCEL: if (!od_config_reader_yes_no(reader, &rule->pool->cancel)) { return NOT_OK_RESPONSE; } continue; /* pool_rollback */ case OD_LPOOL_ROLLBACK: if (!od_config_reader_yes_no(reader, &rule->pool->rollback)) { return NOT_OK_RESPONSE; } continue; /* pool_reset_timeout_ms */ case OD_LPOOL_RESET_TIMEOUT_MS: if (!od_config_reader_number64( reader, &rule->pool->reset_timeout_ms)) { return NOT_OK_RESPONSE; } continue; case OD_LPOOL_RESERVE_PREPARED_STATEMENT: if (!od_config_reader_yes_no( reader, &rule->pool->reserve_prepared_statement)) { return NOT_OK_RESPONSE; } continue; /* pool_pin_on_listen */ case OD_LPOOL_PIN_ON_LISTEN: if (!od_config_reader_yes_no( reader, &rule->pool->pin_on_listen)) { return NOT_OK_RESPONSE; } continue; /* pool_attach_check */ case OD_LPOOL_ATTACH_CHECK: if (!od_config_reader_yes_no( reader, &rule->pool->attach_check)) { return NOT_OK_RESPONSE; } continue; /* pool_notice_after_waiting_ms */ case OD_LPOOL_NOTICE_AFTER_WAITING_MS: if (!od_config_reader_number( reader, &rule->pool->notice_after_waiting_ms)) { return NOT_OK_RESPONSE; } if (rule->pool->notice_after_waiting_ms < 0) { od_config_reader_error( reader, &token, "invalid value for pool_notice_after_waiting_ms"); return NOT_OK_RESPONSE; } continue; /* pool_client_idle_timeout */ case OD_LPOOL_CLIENT_IDLE_TIMEOUT: if (!od_config_reader_number64( reader, &rule->pool->client_idle_timeout)) { return NOT_OK_RESPONSE; } rule->pool->client_idle_timeout *= interval_usec; continue; /* pool_idle_in_transaction_timeout */ case OD_LPOOL_IDLE_IN_TRANSACTION_TIMEOUT: if (!od_config_reader_number64( reader, &rule->pool->idle_in_transaction_timeout)) { return NOT_OK_RESPONSE; } rule->pool->idle_in_transaction_timeout *= interval_usec; continue; /* shared_pool */ case OD_LSHARED_POOL: { char *name = NULL; if (!od_config_reader_string(reader, &name)) { return NOT_OK_RESPONSE; } od_shared_pool_t *sp = od_config_reader_shared_pool_find(reader, name); if (sp == NULL) { od_config_reader_error( reader, &token, "unknown shared pool '%s'", name); od_free(name); return NOT_OK_RESPONSE; } od_free(name); rule->shared_pool = od_shared_pool_ref(sp); continue; } /* storage */ case OD_LSTORAGE: if (!od_config_reader_string(reader, &rule->storage_name)) { return NOT_OK_RESPONSE; } continue; /* storage_database */ case OD_LSTORAGE_DB: if (!od_config_reader_string(reader, &rule->storage_db)) { return NOT_OK_RESPONSE; } continue; /* storage_user */ case OD_LSTORAGE_USER: if (!od_config_reader_string(reader, &rule->storage_user)) { return NOT_OK_RESPONSE; } rule->storage_user_len = strlen(rule->storage_user); continue; /* storage_password */ case OD_LSTORAGE_PASSWORD: if (!od_config_reader_string(reader, &rule->storage_password)) { return NOT_OK_RESPONSE; } rule->storage_password_len = strlen(rule->storage_password); continue; /* log_debug */ case OD_LLOG_DEBUG: if (!od_config_reader_yes_no(reader, &rule->log_debug)) { return NOT_OK_RESPONSE; } continue; /* log_query */ case OD_LLOG_QUERY: if (!od_config_reader_yes_no(reader, &rule->log_query)) { return NOT_OK_RESPONSE; } continue; case OD_LLDAP_ENDPOINT_NAME: { #ifdef LDAP_FOUND if (!od_config_reader_string( reader, &rule->ldap_endpoint_name)) { return NOT_OK_RESPONSE; } od_ldap_endpoint_t *le = od_ldap_endpoint_find( &reader->rules->ldap_endpoints, rule->ldap_endpoint_name); if (le == NULL) { od_config_reader_error( reader, NULL, "ldap endpoint %s is unknown", rule->ldap_endpoint_name); return NOT_OK_RESPONSE; } rule->ldap_endpoint = od_ldap_endpoint_ref(le); continue; #else od_config_reader_error( reader, NULL, "ldap is not supported, check if ldap library is available on the system"); return NOT_OK_RESPONSE; #endif } case OD_LLDAP_STORAGE_CREDENTIALS_ATTR: { #ifdef LDAP_FOUND if (!od_config_reader_string( reader, &rule->ldap_storage_credentials_attr)) { return NOT_OK_RESPONSE; } if (rule->ldap_endpoint_name == NULL) { od_config_reader_error( reader, NULL, "ldap_endpoint_name is not defined for rule with ldap_storage_credentials_attr '%s'", rule->ldap_storage_credentials_attr); return NOT_OK_RESPONSE; } if (strlen(rule->ldap_storage_credentials_attr) == 0) { od_config_reader_error( reader, NULL, "ldap_storage_credentials_attr cannot be empty for rule with ldap_endpoint_name '%s'", rule->ldap_endpoint_name); return NOT_OK_RESPONSE; } continue; #else od_config_reader_error( reader, NULL, "ldap is not supported, check if ldap library is available on the system"); return NOT_OK_RESPONSE; #endif } case OD_LLDAP_STORAGE_CREDENTIALS: { #ifdef LDAP_FOUND if (od_config_reader_ldap_storage_credentials( reader, rule) != OK_RESPONSE) { return NOT_OK_RESPONSE; } if (rule->ldap_storage_credentials_attr == NULL) { od_config_reader_error( reader, NULL, "ldap_storage_credentials_attr is not defined for rule with ldap_endpoint_name '%s'", rule->ldap_endpoint_name); return NOT_OK_RESPONSE; } continue; #else od_config_reader_error( reader, NULL, "ldap is not supported, check if ldap library is available on the system"); return NOT_OK_RESPONSE; #endif } case OD_LWATCHDOG_LAG_QUERY: if (watchdog == NULL) { od_config_reader_error( reader, NULL, "watchdog settings specified for non-watchdog route"); return NOT_OK_RESPONSE; } if (!od_config_reader_string(reader, &watchdog->query)) { return NOT_OK_RESPONSE; } continue; case OD_LWATCHDOG_LAG_INTERVAL: if (watchdog == NULL) { od_config_reader_error( reader, NULL, "watchdog settings specified for non-watchdog route"); return NOT_OK_RESPONSE; } if (!od_config_reader_number(reader, &watchdog->interval)) { return NOT_OK_RESPONSE; } continue; case OD_LCATCHUP_TIMEOUT: if (!od_config_reader_number(reader, &rule->catchup_timeout)) { return NOT_OK_RESPONSE; } continue; case OD_LCATCHUP_CHECKS: if (!od_config_reader_number(reader, &rule->catchup_checks)) { return NOT_OK_RESPONSE; } continue; /* options */ case OD_LOPTIONS: if (od_config_reader_pgoptions(reader, &rule->vars) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } continue; /* backend startup options */ case OD_LBACKEND_STARTUP_OPTIONS: if (od_config_reader_backend_pgoptions(reader, rule) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } continue; /* group_query */ case OD_LGROUP_QUERY: if (rule->group == NULL) { od_config_reader_error( reader, NULL, "group settings specified for non-group route"); return NOT_OK_RESPONSE; } if (!od_config_reader_string( reader, &rule->group->group_query)) { return NOT_OK_RESPONSE; } continue; case OD_LGROUP_QUERY_USER: if (rule->group == NULL) { od_config_reader_error( reader, NULL, "group settings specified for non-group route"); return NOT_OK_RESPONSE; } if (!od_config_reader_string( reader, &rule->group->group_query_user)) { return NOT_OK_RESPONSE; } continue; case OD_LGROUP_QUERY_DB: if (rule->group == NULL) { od_config_reader_error( reader, NULL, "group settings specified for non-group route"); return NOT_OK_RESPONSE; } if (!od_config_reader_string( reader, &rule->group->group_query_db)) { return NOT_OK_RESPONSE; } continue; default: return NOT_OK_RESPONSE; } } return NOT_OK_RESPONSE; } static int od_config_reader_address_string(od_config_reader_t *reader, od_address_range_t *return_range) { /* address and mask or default */ char *addr_str = NULL; char *mask_str = NULL; od_address_range_t address_range; address_range.string_value = NULL; address_range.string_value_len = 0; address_range.is_default = 0; address_range.is_hostname = 0; if (!od_config_reader_string(reader, &address_range.string_value)) { return NOT_OK_RESPONSE; } if (address_range.is_default == 0) { addr_str = od_strdup(address_range.string_value); mask_str = strchr(addr_str, '/'); if (mask_str) { *mask_str++ = 0; } if (od_address_read(&address_range.addr, addr_str) == NOT_OK_RESPONSE) { int is_valid_hostname = od_address_hostname_validate( reader, address_range.string_value); if (is_valid_hostname == -1) { od_config_reader_error( reader, NULL, "could not compile regex"); return NOT_OK_RESPONSE; } else if (is_valid_hostname == 0) { address_range.is_hostname = 1; } else { od_config_reader_error(reader, NULL, "invalid address"); return NOT_OK_RESPONSE; } } else if (mask_str) { if (od_address_range_read_prefix(&address_range, mask_str) == -1) { od_config_reader_error( reader, NULL, "invalid network prefix length"); return NOT_OK_RESPONSE; } } else { od_config_reader_error(reader, NULL, "expected network mask"); return NOT_OK_RESPONSE; } } address_range.string_value_len = strlen(address_range.string_value); *return_range = address_range; od_free(addr_str); return OK_RESPONSE; } static int od_config_reader_route_additional(od_config_reader_t *reader, od_address_range_t *address_range, od_rule_conn_type_t *conn_type, const char *dbname, const char *user) { int address_defined = 0; int conn_type_defined = 0; while (1) { if (od_config_reader_is(reader, OD_PARSER_SYMBOL)) { /* the '{' will be parsed in caller */ return OK_RESPONSE; } if (od_config_reader_is(reader, OD_PARSER_STRING)) { if (address_defined) { goto address_redefinition; } address_defined = 1; /* destroy previously allocated default address */ od_address_range_destroy(address_range); if (od_config_reader_address_string(reader, address_range)) { return NOT_OK_RESPONSE; } continue; } od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_KEYWORD) { od_config_reader_error( reader, &token, "expected default/local/host/hostssl/hostnossl keyword"); return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unexpected keyword"); return NOT_OK_RESPONSE; } switch (keyword->id) { case OD_LDEFAULT: if (address_defined) { goto address_redefinition; } address_defined = 1; /* nothing to do - it is default address that is already contained in address_range */ continue; case OD_LLOCAL: if (conn_type_defined) { goto conn_type_redefinition; } conn_type_defined = 1; *conn_type = OD_RULE_CONN_TYPE_LOCAL; continue; case OD_LHOST: if (conn_type_defined) { goto conn_type_redefinition; } conn_type_defined = 1; *conn_type = OD_RULE_CONN_TYPE_HOST; continue; case OD_LHOSTSSL: if (conn_type_defined) { goto conn_type_redefinition; } conn_type_defined = 1; *conn_type = OD_RULE_CONN_TYPE_HOSTSSL; continue; case OD_LHOSTNOSSL: if (conn_type_defined) { goto conn_type_redefinition; } conn_type_defined = 1; *conn_type = OD_RULE_CONN_TYPE_HOSTNOSSL; continue; default: od_errorf( reader->error, "unexpected keyword at route '%s.%s' definition", dbname, user); return NOT_OK_RESPONSE; } } abort(); return NOT_OK_RESPONSE; address_redefinition: od_errorf(reader->error, "redefinition of address for route '%s.%s'", dbname, user); return NOT_OK_RESPONSE; conn_type_redefinition: od_errorf(reader->error, "redefinition of connection type for route '%s.%s'", dbname, user); return NOT_OK_RESPONSE; } static int od_config_reader_route(od_config_reader_t *reader, char *db_name, int db_is_default, od_extension_t *extensions) { char *user_name = NULL; int user_is_default = 0; /* user name or default */ if (od_config_reader_is(reader, OD_PARSER_STRING)) { if (!od_config_reader_string(reader, &user_name)) { return NOT_OK_RESPONSE; } } else { if (!od_config_reader_keyword( reader, &od_config_keywords[OD_LDEFAULT])) { return NOT_OK_RESPONSE; } user_is_default = 1; user_name = od_strdup("default_user"); if (user_name == NULL) { return NOT_OK_RESPONSE; } } od_address_range_t address_range = od_address_range_create_default(); od_rule_conn_type_t conn_type = OD_RULE_CONN_TYPE_DEFAULT; if (od_config_reader_route_additional(reader, &address_range, &conn_type, db_name, user_name)) { return NOT_OK_RESPONSE; } /* ensure rule does not exists and add new rule */ od_rule_t *rule; rule = od_rules_add_new_rule(reader->rules, db_name, db_is_default, user_name, user_is_default, &address_range, conn_type, 0 /* pool_internal */); od_free(user_name); od_address_range_destroy(&address_range); if (!rule) { od_errorf(reader->error, "route '%s.%s': is redefined", db_name, user_name); return NOT_OK_RESPONSE; } /* { */ if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } /* unreach */ return od_config_reader_rule_settings(reader, rule, extensions, NULL); } static int od_config_reader_group(od_config_reader_t *reader, char *db_name, int db_is_default, od_group_t *group, od_extension_t *extensions) { /* group name */ char *group_name = NULL; if (!od_config_reader_is(reader, OD_PARSER_STRING)) { return NOT_OK_RESPONSE; } if (!od_config_reader_string(reader, &group_name)) { return NOT_OK_RESPONSE; } /* TODO: need to find a way to create internal rules for a specific database */ char route_usr[strlen("group_") + strlen(group_name) + 1]; char route_db[strlen(db_name) + 1]; snprintf(route_usr, sizeof route_usr, "%s%s", "group_", group_name); snprintf(route_db, sizeof route_db, "%s", db_name); od_address_range_t address_range = od_address_range_create_default(); od_rule_conn_type_t conn_type = OD_RULE_CONN_TYPE_DEFAULT; if (od_config_reader_route_additional( reader, &address_range, &conn_type, route_db, route_usr)) { return NOT_OK_RESPONSE; } od_rule_t *rule; rule = od_rules_add_new_rule(reader->rules, route_db, db_is_default, route_usr, 0 /* user_is_default */, &address_range, conn_type, 0 /* pool_internal */); od_address_range_destroy(&address_range); if (!rule) { od_errorf(reader->error, "route '%s.%s': is redefined", route_usr, route_usr); return NOT_OK_RESPONSE; } group->group_name = od_strdup(group_name); group->route_usr = od_strdup(rule->user_name); group->route_db = od_strdup(rule->db_name); rule->group = group; rule->users_in_group = 0; rule->user_names = NULL; /* { */ if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } /* unreach */ if (od_config_reader_rule_settings(reader, rule, extensions, NULL) == NOT_OK_RESPONSE) { goto error; } od_free(group_name); /* force several settings */ group->storage_db = od_strdup(rule->storage_db); group->storage_user = od_strdup(rule->storage_user); rule->pool->routing = OD_RULE_POOL_CLIENT_VISIBLE; rule->users_in_group = 0; rule->user_names = NULL; return OK_RESPONSE; error: od_free(group_name); return NOT_OK_RESPONSE; } static inline int od_config_reader_watchdog(od_config_reader_t *reader, od_storage_watchdog_t *watchdog, od_extension_t *extensions) { watchdog->route_usr = "watchdog_int"; watchdog->route_db = "watchdog_int"; /* ensure rule does not exists and add new rule */ od_rule_t *rule; od_address_range_t address_range = od_address_range_create_default(); rule = od_rules_add_new_rule(reader->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) { od_errorf(reader->error, "route '%s.%s': is redefined", watchdog->route_db, watchdog->route_usr); return NOT_OK_RESPONSE; } /* { */ if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } /* unreach */ if (od_config_reader_rule_settings(reader, rule, extensions, watchdog) == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* force several settings */ watchdog->storage_db = rule->storage_db; watchdog->storage_user = rule->storage_user; rule->pool->routing = OD_RULE_POOL_INTERNAL; return OK_RESPONSE; } #ifdef LDAP_FOUND static inline od_retcode_t od_config_reader_ldap_endpoint(od_config_reader_t *reader) { od_ldap_endpoint_t *ldap_current; ldap_current = od_ldap_endpoint_alloc(); if (!ldap_current) { goto error; } /* name */ if (!od_config_reader_string(reader, &ldap_current->name)) { goto error; } if (od_ldap_endpoint_find(&reader->rules->ldap_endpoints, ldap_current->name) != NULL) { od_config_reader_error(reader, NULL, "duplicate ldap endpoint definition: %s", ldap_current->name); goto error; } od_rules_ldap_endpoint_add(reader->rules, ldap_current); /* { */ if (!od_config_reader_symbol(reader, '{')) { goto error; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { goto init; } /* fall through */ case OD_PARSER_KEYWORD: break; default: od_config_reader_error(reader, &token, "unexpected symbol or token"); goto error; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); return NOT_OK_RESPONSE; } switch (keyword->id) { case OD_LLDAP_SERVER: { if (!od_config_reader_string( reader, &ldap_current->ldapserver)) { goto error; } } break; case OD_LLDAP_PORT: { if (!od_config_reader_number64( reader, &ldap_current->ldapport)) { goto error; } } break; case OD_LLDAP_PREFIX: { if (!od_config_reader_string( reader, &ldap_current->ldapprefix)) { goto error; } } break; case OD_LLDAP_SUFFIX: { if (!od_config_reader_string( reader, &ldap_current->ldapsuffix)) { goto error; } } break; case OD_LLDAP_SEARCH_ATTRIBUTE: { if (!od_config_reader_string( reader, &ldap_current->ldapsearchattribute)) { goto error; } } break; case OD_LLDAP_SCOPE: { if (!od_config_reader_string( reader, &ldap_current->ldapscope)) { goto error; } } break; case OD_LLDAP_SCHEME: { if (!od_config_reader_string( reader, &ldap_current->ldapscheme)) { goto error; } } break; case OD_LLDAP_BASEDN: { if (!od_config_reader_string( reader, &ldap_current->ldapbasedn)) { goto error; } } break; case OD_LLDAP_BINDDN: { if (!od_config_reader_string( reader, &ldap_current->ldapbinddn)) { goto error; } } break; case OD_LLDAP_BIND_PASSWD: { if (!od_config_reader_string( reader, &ldap_current->ldapbindpasswd)) { goto error; } } break; case OD_LLDAP_SEARCH_FILTER: { if (!od_config_reader_string( reader, &ldap_current->ldapsearchfilter)) { goto error; } } break; } } init: if (od_ldap_endpoint_prepare(ldap_current) != OK_RESPONSE) { od_config_reader_error(reader, NULL, "failed to initialize ldap endpoint"); goto error; } /* unreach */ return OK_RESPONSE; error: if (ldap_current) { od_ldap_endpoint_free(ldap_current); } return NOT_OK_RESPONSE; } #endif static inline od_retcode_t od_config_reader_module(od_config_reader_t *reader, od_extension_t *ext) { char *module_path = NULL; int rc; rc = od_config_reader_string(reader, &module_path); if (rc == -1) { return rc; } od_module_t *module = od_modules_find(ext->modules, module_path); if (module != NULL) { od_free(module_path); /* skip all related conf */ /* { */ if (!od_config_reader_symbol(reader, '{')) { return NOT_OK_RESPONSE; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { return 0; } /* fall through */ default: continue; } } return OK_RESPONSE; } if (od_target_module_add(NULL, ext->modules, module_path) == OD_MODULE_CB_FAIL_RETCODE) { goto error; } module = od_modules_find(ext->modules, module_path); assert(module != NULL); if (module->config_module_init_db == NULL) { goto error; } rc = module->config_module_init_db(reader); if (rc != OD_MODULE_CB_OK_RETCODE) { goto error; } return OK_RESPONSE; error: od_free(module_path); return NOT_OK_RESPONSE; } static int od_config_reader_database(od_config_reader_t *reader, od_extension_t *extensions) { char *db_name = NULL; int db_is_default = 0; /* name or default */ if (od_config_reader_is(reader, OD_PARSER_STRING)) { if (!od_config_reader_string(reader, &db_name)) { return NOT_OK_RESPONSE; } } else { if (!od_config_reader_keyword( reader, &od_config_keywords[OD_LDEFAULT])) { return NOT_OK_RESPONSE; } db_is_default = 1; db_name = od_strdup("default_db"); if (db_name == NULL) { return NOT_OK_RESPONSE; } } /* { */ if (!od_config_reader_symbol(reader, '{')) { goto error; } for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: od_config_reader_error(reader, &token, "unexpected end of config file"); goto error; case OD_PARSER_SYMBOL: /* } */ if (token.value.num == '}') { od_free(db_name); return 0; } /* fall through */ default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); goto error; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); goto error; } switch (keyword->id) { /* user */ case OD_LUSER: rc = od_config_reader_route(reader, db_name, db_is_default, extensions); if (rc == -1) { goto error; } continue; case OD_LGROUP:; od_group_t *group; group = od_rules_group_allocate(reader->global); if (group == NULL) { return NOT_OK_RESPONSE; } rc = od_config_reader_group(reader, db_name, db_is_default, group, extensions); if (rc == -1) { goto error; } continue; default: od_config_reader_error(reader, &token, "unexpected parameter"); goto error; } } /* unreach */ return NOT_OK_RESPONSE; error: od_free(db_name); return NOT_OK_RESPONSE; } static int od_config_reader_hba_import(od_config_reader_t *config_reader) { od_config_reader_t reader; memset(&reader, 0, sizeof(reader)); reader.config = config_reader->config; reader.error = config_reader->error; reader.hba_rules = config_reader->hba_rules; int rc; rc = od_config_reader_open(&reader, config_reader->config->hba_file); if (rc == -1) { return -1; } rc = od_hba_reader_parse(&reader); od_config_reader_close(&reader); return rc; } static void od_config_setup_default_tcp_usr_timeout(od_config_t *config) { 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); } } static int od_config_reader_affinity(od_config_reader_t *reader, od_affinity_config_t *config) { od_token_t tok; int rc; rc = od_parser_next(&reader->parser, &tok); char buf[256]; switch (rc) { case OD_PARSER_STRING: rc = od_affinity_config_parse(tok.value.string.pointer, tok.value.string.size, config, buf, sizeof(buf)); if (rc != 0) { od_config_reader_error(reader, &tok, "%s", buf); return 0; } break; default: od_config_reader_error( reader, &tok, "unexpected type of value for cpu_affinity"); return 0; } return 1; } static int od_config_reader_parse(od_config_reader_t *reader, od_extension_t *extensions) { od_config_t *config = reader->config; for (;;) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_EOF: goto success; case OD_PARSER_KEYWORD: break; default: od_config_reader_error( reader, &token, "incorrect or unexpected parameter"); goto error; } od_keyword_t *keyword; keyword = od_keyword_match(od_config_keywords, &token); if (keyword == NULL) { od_config_reader_error(reader, &token, "unknown parameter"); goto error; } switch (keyword->id) { /* include */ case OD_LINCLUDE: { char *config_file = NULL; if (!od_config_reader_string(reader, &config_file)) { return NOT_OK_RESPONSE; } rc = od_config_reader_import( reader->config, reader->rules, reader->error, extensions, reader->global, reader->hba_rules, config_file); od_free(config_file); if (rc == -1) { goto error; } continue; } /* daemonize */ case OD_LDAEMONIZE: if (!od_config_reader_yes_no(reader, &config->daemonize)) { goto error; } continue; /* priority */ case OD_LPRIORITY: if (!od_config_reader_number(reader, &config->priority)) { goto error; } continue; /* sequential_routing */ case OD_LSEQROUTING: if (!od_config_reader_yes_no( reader, &config->sequential_routing)) { goto error; } continue; /* pid_file */ case OD_LPID_FILE: if (!od_config_reader_string(reader, &config->pid_file)) { goto error; } continue; /* unix_socket_dir */ case OD_LUNIX_SOCKET_DIR: if (!od_config_reader_string( reader, &config->unix_socket_dir)) { goto error; } continue; /* unix_socket_mode */ case OD_LUNIX_SOCKET_MODE: if (!od_config_reader_string( reader, &config->unix_socket_mode)) { goto error; } continue; /* locks_dir */ case OD_LLOCKS_DIR: if (!od_config_reader_string(reader, &config->locks_dir)) { goto error; } continue; /* external_auth_socket_path */ case OD_LEXTERNAL_AUTH_SOCKET_PATH: if (!od_config_reader_string( reader, &config->external_auth_socket_path)) { goto error; } continue; /* enable_online_restart */ case OD_LENABLE_ONLINE_RESTART: if (!od_config_reader_yes_no( reader, &config->enable_online_restart_feature)) { goto error; } continue; /* availability_zone */ case OD_LAVAILABILITY_ZONE: { char *val = NULL; if (!od_config_reader_string(reader, &val)) { goto error; } if (strlen(val) > OD_MAX_AVAILABILITY_ZONE_LENGTH - 1) { od_config_reader_error( reader, &token, "availability zone name is too large"); goto error; } strcpy(config->availability_zone, val); od_free(val); continue; } case OD_LVIRTUAL_PROC: if (!od_config_reader_yes_no( reader, &config->virtual_processing)) { goto error; } continue; /* * online_restart_drop_options * conn_drop_options * * this are sinonimous, for backward capitibility */ case OD_LONLINE_RESTART_DROP_OPTIONS: /* fallthrough */ case OD_LCONN_DROP_OPTIONS: rc = od_config_reader_conn_drop_options(reader); if (rc != OK_RESPONSE) { goto error; } continue; /* graceful_die_on_errors */ case OD_LGRACEFUL_DIE_ON_ERRORS: if (!od_config_reader_yes_no( reader, &config->graceful_die_on_errors)) { goto error; } continue; /* graceful_shutdown_timeout_ms */ case OD_LGRACEFUL_SHUTDOWN_TIMEOUT_MS: if (!od_config_reader_number( reader, &config->graceful_shutdown_timeout_ms)) { goto error; } if (config->graceful_shutdown_timeout_ms < 0) { od_config_reader_error( reader, &token, "graceful shutdown timeout must be non-negative"); goto error; } continue; /* soft_oom */ case OD_LSOFT_OOM: if (od_config_reader_soft_oom_options(reader) != OK_RESPONSE) { goto error; } continue; case OD_LBINDWITH_REUSEPORT: if (!od_config_reader_yes_no( reader, &config->bindwith_reuseport)) { goto error; } continue; /* enable_host_watcher */ case OD_LENABLE_HOST_WATCHER: if (!od_config_reader_yes_no( reader, &config->host_watcher_enabled)) { goto error; } continue; /* log_debug */ case OD_LLOG_DEBUG: if (!od_config_reader_yes_no(reader, &config->log_debug)) { goto error; } continue; /* log_stdout */ case OD_LLOG_TO_STDOUT: if (!od_config_reader_yes_no(reader, &config->log_to_stdout)) { goto error; } continue; /* log_config */ case OD_LLOG_CONFIG: if (!od_config_reader_yes_no(reader, &config->log_config)) { goto error; } continue; /* log_session */ case OD_LLOG_SESSION: if (!od_config_reader_yes_no(reader, &config->log_session)) { goto error; } continue; /* log_query */ case OD_LLOG_QUERY: if (!od_config_reader_yes_no(reader, &config->log_query)) { goto error; } continue; /* log_stats */ case OD_LLOG_STATS: if (!od_config_reader_yes_no(reader, &config->log_stats)) { goto error; } continue; /* log_async */ case OD_LLOG_ASYNC: if (!od_config_reader_yes_no(reader, &config->log_async)) { goto error; } continue; /* log_queue_depth */ case OD_LLOG_QUEUE_DEPTH: if (!od_config_reader_number( reader, &config->log_queue_depth)) { goto error; } continue; /* log_format */ case OD_LLOG_FORMAT: if (!od_config_reader_string(reader, &config->log_format)) { goto error; } continue; /* log_file */ case OD_LLOG_FILE: if (!od_config_reader_string(reader, &config->log_file)) { goto error; } continue; /* log_syslog */ case OD_LLOG_SYSLOG: if (!od_config_reader_yes_no(reader, &config->log_syslog)) { goto error; } continue; /* log_syslog_ident */ case OD_LLOG_SYSLOG_IDENT: if (!od_config_reader_string( reader, &config->log_syslog_ident)) { goto error; } continue; /* log_syslog_facility */ case OD_LLOG_SYSLOG_FACILITY: if (!od_config_reader_string( reader, &config->log_syslog_facility)) { goto error; } continue; /* stats_interval */ case OD_LSTATS_INTERVAL: if (!od_config_reader_number(reader, &config->stats_interval)) { goto error; } continue; /* smart_search_path_enquoting */ case OD_LSMART_SEARCH_PATH_ENQUOTING: if (!od_config_reader_yes_no( reader, &config->smart_search_path_enquoting)) { goto error; } continue; /* client_max */ case OD_LCLIENT_MAX: if (!od_config_reader_number(reader, &config->client_max)) { goto error; } if (config->client_max > 0) { config->client_max_set = 1; } continue; /* client_max_routing */ case OD_LCLIENT_MAX_ROUTING: if (!od_config_reader_number( reader, &config->client_max_routing)) { goto error; } continue; /* server_login_retry */ case OD_LSERVER_LOGIN_RETRY: if (!od_config_reader_number( reader, &config->server_login_retry)) { goto error; } continue; /* readahead */ case OD_LREADAHEAD: if (!od_config_reader_number(reader, &config->readahead)) { goto error; } continue; /* nodelay */ case OD_LNODELAY: if (!od_config_reader_yes_no(reader, &config->nodelay)) { goto error; } continue; /* disable_nolinger */ case OD_LDISABLE_NOLINGER: if (!od_config_reader_yes_no( reader, &config->disable_nolinger)) { goto error; } continue; /* keepalive */ case OD_LKEEPALIVE: if (!od_config_reader_number(reader, &config->keepalive)) { goto error; } continue; /* keepalive_keep_interval */ case OD_LKEEPALIVE_INTERVAL: if (!od_config_reader_number( reader, &config->keepalive_keep_interval)) { goto error; } continue; /* keepalive_probes */ case OD_LKEEPALIVE_PROBES: if (!od_config_reader_number( reader, &config->keepalive_probes)) { goto error; } continue; /* max_sigterms_to_die */ case OD_LMAX_SIGTERMS_TO_DIE: if (!od_config_reader_number( reader, &config->max_sigterms_to_die)) { return NOT_OK_RESPONSE; } continue; /* backend_connect_timeout_ms */ case OD_LBACKEND_CONNECT_TIMEOUT_MS: if (!od_config_reader_number( reader, &config->backend_connect_timeout_ms)) { goto error; } continue; /* cancel_timeout_ms */ case OD_LCANCEL_TIMEOUT_MS: if (!od_config_reader_number( reader, &config->cancel_timeout_ms)) { goto error; } continue; /* keepalive_usr_timeout */ case OD_LKEEPALIVE_USR_TIMEOUT: if (!od_config_reader_number( reader, &config->keepalive_usr_timeout)) { goto error; } continue; /* log_stats_prom */ case OD_LLOG_GENERAL_STATS_PROM: { if (!od_config_reader_yes_no( reader, &config->log_general_stats_prom)) { goto error; } continue; } case OD_LLOG_ROUTE_STATS_PROM: { if (!od_config_reader_yes_no( reader, &config->log_route_stats_prom)) { goto error; } continue; } case OD_LPROMHTTP_PORT: { #ifndef PROMHTTP_FOUND od_config_reader_error( reader, &token, "promhttp_server_port read failed PROMHTTP_FOUND not set"); goto error; #endif int port; if (!od_config_reader_number(reader, &port)) { goto error; } #ifdef PROMHTTP_FOUND if (od_prom_set_port( port, ((od_cron_t *)(reader->global->cron)) ->metrics) != OK_RESPONSE) { goto error; } #endif continue; } /* cpu_affinity */ case OD_LCPU_AFFINITY: config->cpu_affinity = od_malloc(sizeof(od_affinity_config_t)); if (config->cpu_affinity == NULL) { od_config_reader_error(reader, &token, "out of memory"); goto error; } od_affinity_config_init(config->cpu_affinity); if (!od_config_reader_affinity(reader, config->cpu_affinity)) { goto error; } continue; /* workers */ case OD_LWORKERS: { od_token_t tok; int rc; rc = od_parser_next(&reader->parser, &tok); switch (rc) { case OD_PARSER_NUM: { config->workers = tok.value.num; } break; case OD_PARSER_STRING: { if (strncmp(tok.value.string.pointer, "auto", tok.value.string.size) == 0) { config->workers = (1 + od_get_ncpu()) >> 1; break; } } /* fall through */ default: od_config_reader_error( reader, &tok, "expected 'number' or '\"auto\"'"); goto error; } } continue; /* resolvers */ case OD_LRESOLVERS: if (!od_config_reader_number(reader, &config->resolvers)) { goto error; } continue; /* pipeline */ case OD_LPIPELINE: /* fallthrough */ case OD_LCACHE: /* cache */ /* fallthrough */ case OD_LCACHE_CHUNK: /* cache_chunk */ /* fallthrough */ case OD_LPACKET_WRITE_QUEUE: /* packet write queue */ /* fallthrough */ case OD_LPACKET_READ_SIZE: { /* deprecated */ int unused; if (!od_config_reader_number(reader, &unused)) { goto error; } continue; } /* cache_msg_gc_size */ case OD_LCACHE_MSG_GC_SIZE: if (!od_config_reader_number( reader, &config->cache_msg_gc_size)) { goto error; } continue; /* cache_coroutine */ case OD_LCACHE_COROUTINE: if (!od_config_reader_number( reader, &config->cache_coroutine)) { goto error; } continue; /* coroutine_stack_size */ case OD_LCOROUTINE_STACK_SIZE: if (!od_config_reader_number( reader, &config->coroutine_stack_size)) { goto error; } continue; /* listen */ case OD_LLISTEN: rc = od_config_reader_listen(reader); if (rc == -1) { goto error; } continue; /* storage */ case OD_LSTORAGE: rc = od_config_reader_storage(reader, extensions); if (rc == -1) { goto error; } continue; /* shared_pool */ case OD_LSHARED_POOL: rc = od_config_reader_shared_pool(reader); if (rc == -1) { goto error; } continue; /* database */ case OD_LDATABASE: rc = od_config_reader_database(reader, extensions); if (rc == -1) { goto error; } continue; /* ldap service */ case OD_LLDAP_ENDPOINT: { #ifdef LDAP_FOUND rc = od_config_reader_ldap_endpoint(reader); if (rc != OK_RESPONSE) { goto error; } continue; #else od_config_reader_error(reader, &token, "unexpected parameter"); goto error; #endif } /* module */ case OD_LMODULE: { rc = od_config_reader_module(reader, extensions); if (rc == -1) { goto error; } continue; } case OD_LHBA_FILE: { rc = od_config_reader_string(reader, &config->hba_file); if (rc == -1) { goto error; } rc = od_config_reader_hba_import(reader); if (rc == -1) { goto error; } continue; } case OD_LGROUP_CHECKER_INTERVAL: rc = od_config_reader_number( reader, &config->group_checker_interval); if (rc == -1) { goto error; } continue; default: od_config_reader_error(reader, &token, "unexpected parameter"); goto error; } } /* unreach */ return NOT_OK_RESPONSE; error: return NOT_OK_RESPONSE; success: if (!config->client_max_routing) { config->client_max_routing = config->workers * 64; } od_config_setup_default_tcp_usr_timeout(config); return 0; } int od_config_reader_import(od_config_t *config, od_rules_t *rules, od_error_t *error, od_extension_t *extensions, od_global_t *global, od_hba_rules_t *hba_rules, char *config_file) { od_config_reader_t reader; memset(&reader, 0, sizeof(reader)); reader.error = error; reader.config = config; reader.rules = rules; reader.hba_rules = hba_rules; reader.global = global; if (regcomp(&reader.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)) { return NOT_OK_RESPONSE; } int rc; rc = od_config_reader_open(&reader, config_file); if (rc == NOT_OK_RESPONSE) { goto finish; } rc = od_config_reader_parse(&reader, extensions); od_config_reader_close(&reader); finish: regfree(&reader.rfc952_hostname_regex); return rc; } odyssey-1.5.1-rc8/sources/console.c000066400000000000000000002103141517700303500172020ustar00rootroot00000000000000 /* * 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 typedef enum { OD_LKILL_CLIENT, OD_LRELOAD, OD_LSHOW, OD_LSTATS, OD_LSERVERS, OD_LSERVER_PREP_STMTS, OD_LCLIENTS, OD_LLISTS, OD_LHELP, OD_LSET, OD_LCREATE, OD_LDROP, OD_LPOOLS, OD_LPOOLS_EXTENDED, OD_LDATABASES, OD_LMODULE, OD_LERRORS, OD_LERRORS_PER_ROUTE, OD_LFRONTEND, OD_LROUTER, OD_LVERSION, OD_LVERSION_EXTENDED, OD_LLISTEN, OD_LSTORAGES, OD_LFDS, OD_LPAUSE, OD_LRESUME, OD_LIS_PAUSED, OD_LHOST_UTILIZATION, OD_LRULES, } od_console_keywords_t; static od_keyword_t od_console_keywords[] = { od_keyword("kill_client", OD_LKILL_CLIENT), od_keyword("reload", OD_LRELOAD), od_keyword("help", OD_LHELP), od_keyword("show", OD_LSHOW), od_keyword("stats", OD_LSTATS), od_keyword("servers", OD_LSERVERS), od_keyword("server_prep_stmts", OD_LSERVER_PREP_STMTS), od_keyword("clients", OD_LCLIENTS), od_keyword("lists", OD_LLISTS), od_keyword("set", OD_LSET), od_keyword("pools", OD_LPOOLS), od_keyword("pools_extended", OD_LPOOLS_EXTENDED), od_keyword("databases", OD_LDATABASES), od_keyword("create", OD_LCREATE), od_keyword("module", OD_LMODULE), od_keyword("errors", OD_LERRORS), od_keyword("errors_per_route", OD_LERRORS_PER_ROUTE), od_keyword("frontend", OD_LFRONTEND), od_keyword("router", OD_LROUTER), od_keyword("drop", OD_LDROP), od_keyword("version", OD_LVERSION), od_keyword("version_extended", OD_LVERSION_EXTENDED), od_keyword("listen", OD_LLISTEN), od_keyword("storages", OD_LSTORAGES), od_keyword("fds", OD_LFDS), od_keyword("pause", OD_LPAUSE), od_keyword("resume", OD_LRESUME), od_keyword("is_paused", OD_LIS_PAUSED), od_keyword("host_utilization", OD_LHOST_UTILIZATION), od_keyword("rules", OD_LRULES), { 0, 0, 0 } }; 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) { 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; /* total_xact_count */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->count_tx); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_query_count */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->count_query); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_server_assignment_count */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->count_wait); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_received */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->recv_client); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_sent */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->recv_server); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_xact_time */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->tx_time); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_query_time */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->query_time); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* total_wait_time */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->wait_time_us); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_xact_count */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->count_tx); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_query_count */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->count_query); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_server_assignment_count */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->count_wait); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_recv */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->recv_client); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_sent */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->recv_server); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_xact_time */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->tx_time); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_query_time */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->query_time); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* avg_wait_time */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, avg->wait_time_us); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* count of backend parse msgs */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->count_parse); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { return NOT_OK_RESPONSE; } /* count of backend parse msgs reuse */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, total->count_parse_reuse); 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) { 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), "%" PRIu64, 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) { 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), "%" PRIu64, 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) { 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|ERRORS|ERRORS_PER_ROUTE|VERSION|LISTEN|STORAGES\n" "\tKILL_CLIENT \n" "\tRELOAD\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) { assert(stream); od_router_t *router = client->global->router; od_cron_t *cron = client->global->cron; if (kiwi_be_write_row_descriptionf( stream, "sllllllllllllllllll", "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") == 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) { 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]; 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), "%" PRIu64, 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), "%" PRIu64, 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) { 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) { 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 */ #ifdef ODYSSEY_VERSION_GIT data_len = od_snprintf(data, sizeof(data), "Odyssey %s (git %s) %s, compiled by %s", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT, ODYSSEY_BUILD_TYPE, ODYSSEY_COMPILER_STRING); #else data_len = od_snprintf(data, sizeof(data), "Odyssey %s %s, compiled by %s", ODYSSEY_VERSION_NUMBER, ODYSSEY_BUILD_TYPE, ODYSSEY_COMPILER_STRING); #endif 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) { 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, 0UL); 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, 0UL); 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, 0UL); rc = kiwi_be_write_data_row_add(stream, offset, data, data_len); if (rc == NOT_OK_RESPONSE) { goto error; } /* maxwait */ data_len = od_snprintf(data, sizeof(data), "%" PRIu64, 0UL); 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, 0UL); 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; } 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, 0UL); 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, 0UL); 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, 0UL); 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) { 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) { 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; } 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 = 13; 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", route->rule->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_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 = *( const od_pstmt_t **)mm_hashmap_kvp_val(server->prep_stmts, 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) { 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) { 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) { 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 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_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; pthread_mutex_lock(&rules->mu); 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; } } pthread_mutex_unlock(&rules->mu); return kiwi_be_write_complete(stream, "SHOW", sizeof("SHOW")); error: pthread_mutex_unlock(&rules->mu); 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", 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), "%lu", 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) { 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) { 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 = od_atomic_u32_of(&router->clients); 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", od_atomic_u32_of(&router->clients_routing)); 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); } 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) { 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) { 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; pthread_mutex_lock(&rules->mu); 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; } } pthread_mutex_unlock(&rules->mu); return kiwi_be_write_complete(stream, "SHOW", 5); error: pthread_mutex_unlock(&rules->mu); return rc; } static inline int od_console_show(od_client_t *client, machine_msg_t *stream, od_parser_t *parser) { assert(stream); od_token_t token; int rc; rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: default: return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_console_keywords, &token); if (keyword == NULL) { return NOT_OK_RESPONSE; } switch (keyword->id) { case OD_LSTATS: return od_console_show_stats(client, stream); case OD_LHELP: return od_console_show_help(stream); case OD_LPOOLS: return od_console_show_pools(client, stream, false); case OD_LPOOLS_EXTENDED: return od_console_show_pools(client, stream, true); case OD_LDATABASES: return od_console_show_databases(client, stream); case OD_LSERVER_PREP_STMTS: return od_console_show_server_prep_stmts(client, stream); case OD_LSERVERS: return od_console_show_servers(client, stream); case OD_LCLIENTS: return od_console_show_clients(client, stream); case OD_LLISTS: return od_console_show_lists(client, stream); case OD_LERRORS: return od_console_show_errors(client, stream); case OD_LERRORS_PER_ROUTE: return od_console_show_errors_per_route(client, stream); case OD_LVERSION: return od_console_show_version(stream); case OD_LVERSION_EXTENDED: return od_console_show_version_extended(stream); case OD_LLISTEN: return od_console_show_listen(client, stream); case OD_LSTORAGES: return od_console_show_storages(client, stream); case OD_LFDS: return od_console_show_fds(client, stream); case OD_LIS_PAUSED: return od_console_show_is_paused(client, stream); case OD_LHOST_UTILIZATION: return od_console_show_host_utilization(client, stream); case OD_LRULES: return od_console_show_rules(stream); } return NOT_OK_RESPONSE; } 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, od_parser_t *parser) { (void)stream; od_token_t token; int rc; rc = od_parser_next(parser, &token); if (rc != OD_PARSER_KEYWORD) { return NOT_OK_RESPONSE; } od_id_t id; if (token.value.string.size != (sizeof(id.id) + 1)) { return NOT_OK_RESPONSE; } memcpy(id.id, token.value.string.pointer + 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"); od_system_config_reload(client->global->system); return kiwi_be_write_complete(stream, "RELOAD", 7); } 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_add_module(od_client_t *client, machine_msg_t *stream, od_parser_t *parser) { assert(stream); od_token_t token; int rc; rc = od_parser_next(parser, &token); od_instance_t *instance = client->global->instance; switch (rc) { case OD_PARSER_STRING: { char module_path[MAX_MODULE_PATH_LEN]; od_token_to_string_dest(&token, module_path); od_log(&instance->logger, "od module dynamic load", NULL, NULL, "loading module with path %s", module_path); int retcode = od_target_module_add( &instance->logger, ((od_extension_t *)client->global->extensions)->modules, module_path); if (retcode == 0) { od_frontend_infof(client, stream, "module was successfully loaded!"); } else { od_frontend_errorf( client, stream, KIWI_SYSTEM_ERROR, "module was NOT successfully loaded! Check logs for details"); } return retcode; } case OD_PARSER_EOF: default: return NOT_OK_RESPONSE; } } static inline int od_console_unload_module(od_client_t *client, machine_msg_t *stream, od_parser_t *parser) { assert(stream); od_token_t token; int rc; rc = od_parser_next(parser, &token); od_instance_t *instance = client->global->instance; switch (rc) { case OD_PARSER_STRING: { char module_path[MAX_MODULE_PATH_LEN]; od_token_to_string_dest(&token, module_path); od_log(&instance->logger, "od module dynamic unload", NULL, NULL, "unloading module with path %s", module_path); int retcode = od_target_module_unload( &instance->logger, ((od_extension_t *)client->global->extensions)->modules, module_path); if (retcode == 0) { od_frontend_infof(client, stream, "module was successfully unloaded!"); } else { od_frontend_errorf(client, stream, KIWI_SYSTEM_ERROR, "module was NOT successfully " "unloaded! Check logs for details"); } return retcode; } case OD_PARSER_EOF: default: return NOT_OK_RESPONSE; } } static inline int od_console_create(od_client_t *client, machine_msg_t *stream, od_parser_t *parser) { assert(stream); od_token_t token; int rc; rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: default: return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_console_keywords, &token); if (keyword == NULL) { return NOT_OK_RESPONSE; } switch (keyword->id) { case OD_LMODULE: return od_console_add_module(client, stream, parser); } return NOT_OK_RESPONSE; } static inline int od_console_drop_server_cb(od_server_t *server, od_attribute_unused() 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, od_parser_t *parser) { (void)client; assert(stream); od_token_t token; int rc; rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_EOF: break; default: return NOT_OK_RESPONSE; } od_router_t *router = client->global->router; void *argv[] = { stream }; od_router_foreach(router, od_console_drop_server, argv); return OK_RESPONSE; } static inline od_retcode_t od_console_drop(od_client_t *client, machine_msg_t *stream, od_parser_t *parser) { assert(stream); od_token_t token; int rc; rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: default: return NOT_OK_RESPONSE; } od_keyword_t *keyword; keyword = od_keyword_match(od_console_keywords, &token); if (keyword == NULL) { return NOT_OK_RESPONSE; } switch (keyword->id) { case OD_LSERVERS: return od_console_drop_servers(client, stream, parser); case OD_LMODULE: return od_console_unload_module(client, stream, parser); default: return NOT_OK_RESPONSE; } return NOT_OK_RESPONSE; } 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_parser_t parser; od_parser_init(&parser, query, query_len); od_token_t token; rc = od_parser_next(&parser, &token); switch (rc) { case OD_PARSER_KEYWORD: break; case OD_PARSER_EOF: default: goto bad_query; } od_keyword_t *keyword; keyword = od_keyword_match(od_console_keywords, &token); if (keyword == NULL) { goto bad_query; } switch (keyword->id) { case OD_LSHOW: rc = od_console_show(client, stream, &parser); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LKILL_CLIENT: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_kill_client(client, stream, &parser); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LRELOAD: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_reload(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LSET: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_set(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LCREATE: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_create(client, stream, &parser); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LDROP: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_drop(client, stream, &parser); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LPAUSE: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_pause(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; case OD_LRESUME: if (client->rule->user_role != OD_RULE_ROLE_ADMIN) { goto incorrect_role; } rc = od_console_resume(client, stream); if (rc == NOT_OK_RESPONSE) { goto bad_query; } break; default: goto bad_query; } 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.5.1-rc8/sources/counter.c000066400000000000000000000033111517700303500172140ustar00rootroot00000000000000 /* * 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(od_atomic_u64_t) * values_count); if (od_unlikely(counter == NULL)) { return NULL; } counter->size = values_count; for (size_t i = 0; i < counter->size; ++i) { od_atomic_u64_set(&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); od_atomic_u64_inc(&counter->value_to_count[value]); } uint64_t od_counter_get_count(od_counter_t *counter, size_t value) { check_value(counter, value); return od_atomic_u64_of(&counter->value_to_count[value]); } od_retcode_t od_counter_reset(od_counter_t *counter, size_t value) { check_value(counter, value); od_atomic_u64_set(&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) { od_atomic_u64_set(&counter->value_to_count[i], 0ULL); } return OK_RESPONSE; } odyssey-1.5.1-rc8/sources/cron.c000066400000000000000000000244171517700303500165100ustar00rootroot00000000000000 /* * 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; if (instance->config.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; od_atomic_u64_t startup_errors = od_atomic_u64_of(&cron->startup_errors); 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", od_atomic_u32_of(&router->clients)); } /* update stats per route and print info */ od_route_pool_stat_cb_t stat_cb; if (!instance->config.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 void od_rules_gc(void) { /* remove all obsolete rules that has no refs on it */ od_global_t *global = od_global_get(); od_router_t *router = global->router; /* * we will do it in two steps: * - create list of obsolete rules * - remove all rules from list * * thats because i dont want to perform free() with locked router */ od_list_t to_delete; od_list_init(&to_delete); od_router_lock(router); od_list_t *i, *n; od_list_foreach_safe (&router->rules.rules, i, n) { od_rule_t *rule = od_container_of(i, od_rule_t, link); if (!rule->obsolete) { continue; } if (rule->refs > 0) { continue; } od_list_unlink(&rule->link); od_list_append(&to_delete, &rule->link); } od_router_unlock(router); od_list_foreach_safe (&to_delete, i, n) { od_rule_t *rule = od_container_of(i, od_rule_t, link); od_list_unlink(&rule->link); od_rules_rule_free(rule); } } 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); /* cleanup unused rules */ od_rules_gc(); } 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(); atomic_store(&cron->online, 1); int stats_tick = 0; for (;;) { if (!atomic_load(&cron->online)) { break; } /* 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); /* 1 second soft interval */ machine_sleep(1000); } /* * 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 atomic_init(&cron->online, 0); cron->can_be_freed = machine_wait_flag_create(); if (cron->can_be_freed == NULL) { 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) { atomic_store(&cron->online, 0); 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); return OK_RESPONSE; } odyssey-1.5.1-rc8/sources/daemon.c000066400000000000000000000010241517700303500167770ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/debugprintf.c000066400000000000000000000002651517700303500200530ustar00rootroot00000000000000#include #include void od_dbg_printf(char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } odyssey-1.5.1-rc8/sources/deploy.c000066400000000000000000000031601517700303500170330ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include static inline int complete_deploy(od_server_t *server, char *context) { if (od_service_stream_server_until_rfq( context, server, 0 /* ignore errors */, 1000) != 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; od_route_t *route = client->route; if (route->id.physical_rep || route->id.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(server, context); } return OK_RESPONSE; } odyssey-1.5.1-rc8/sources/dns.c000066400000000000000000000043621517700303500163300ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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(struct addrinfo *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.5.1-rc8/sources/ejection.c000066400000000000000000000041061517700303500173400ustar00rootroot00000000000000 /* * 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)); pthread_spin_init(&(*info)->mu, PTHREAD_PROCESS_PRIVATE); (*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) { pthread_spin_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; pthread_spin_lock(&info->mu); if (info->limit > 0) { queue_clean(info, now_ms); rc = queue_push(info, now_ms); } else { rc = 1; } pthread_spin_unlock(&info->mu); return rc; } odyssey-1.5.1-rc8/sources/err_logger.c000066400000000000000000000047371517700303500177010ustar00rootroot00000000000000#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->intervals_cnt = 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->intervals_cnt; ++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->intervals_cnt; ++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) { od_counter_inc(l->interval_counters[l->current_interval_num], err_t); return OK_RESPONSE; } od_retcode_t od_err_logger_inc_interval(od_error_logger_t *l) { size_t old = atomic_load(&l->current_interval_num); while (!atomic_compare_exchange_weak(&l->current_interval_num, &old, (old + 1) % l->intervals_cnt)) { OD_ERR_LOGGER_CAS_BACKOFF; } size_t new = (old + 1) % l->intervals_cnt; /* some errors may be lost */ od_counter_reset_all(l->interval_counters[new]); 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->intervals_cnt; ++i) { ret_val += od_counter_get_count(l->interval_counters[i], err_t); } return ret_val; } odyssey-1.5.1-rc8/sources/external_auth.c000066400000000000000000000124351517700303500204070ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/frontend.c000066400000000000000000002157121517700303500173660ustar00rootroot00000000000000 /* * 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) { assert(client->route == NULL); assert(client->server == NULL); od_router_t *router = client->global->router; od_atomic_u32_dec(&router->clients); 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(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; assert(server != NULL); 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; 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 od_frontend_startup(od_client_t *client) { od_instance_t *instance = client->global->instance; machine_msg_t *msg; for (uint32_t startup_attempt = 0; startup_attempt < MAX_STARTUP_ATTEMPTS; startup_attempt++) { msg = od_read_startup( &client->io, client->config_listen->client_login_timeout); if (msg == NULL) { goto error; } int rc = kiwi_be_read_startup(machine_msg_data(msg), machine_msg_size(msg), &client->startup, &client->vars); machine_msg_free(msg); if (rc == -1) { goto error; } if (!client->startup.unsupported_request) { break; } /* not supported 'N' */ 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(&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"); } /* client ssl request */ int rc = od_tls_frontend_accept(client, &instance->logger, client->config_listen, client->tls); if (rc == -1) { goto error; } if (!client->startup.is_ssl_request) { rc = od_compression_frontend_setup( client, client->config_listen, &instance->logger); if (rc == -1) { return -1; } return 0; } /* read startup-cancel message followed after ssl * negotiation */ assert(client->startup.is_ssl_request); msg = od_read_startup(&client->io, client->config_listen->client_login_timeout); if (msg == NULL) { goto error; } rc = kiwi_be_read_startup(machine_msg_data(msg), machine_msg_size(msg), &client->startup, &client->vars); machine_msg_free(msg); if (rc == -1) { od_gerror("startup", client, NULL, "startup packet parse error"); goto error; } rc = od_compression_frontend_setup(client, client->config_listen, &instance->logger); if (rc == -1) { return -1; } 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; od_atomic_u64_inc(&cron->startup_errors); 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) { od_instance_t *instance = client->global->instance; od_router_t *router = client->global->router; od_route_t *route = client->route; bool wait_for_idle = false; for (;;) { od_router_status_t status; status = od_router_attach(router, client, wait_for_idle, &endpoint->address); if (status != OD_ROUTER_OK) { 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); assert(od_server_synchronized(server)); /* connect to server, if necessary */ if (od_backend_not_connected(server)) { int rc; od_atomic_u32_inc(&router->servers_routing); assert(client->config_listen != NULL); rc = od_backend_connect(server, context, route_params, client); od_atomic_u32_dec(&router->servers_routing); 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; } if (od_backend_check_tsa(endpoint, context, server, client, tsa) != OK_RESPONSE) { char addr[256]; 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)); /* push server to router server pool */ od_router_detach(router, client); /* try another host */ return OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH; } return OD_OK; } } od_frontend_status_t od_frontend_attach(od_client_t *client, char *context, kiwi_params_t *route_params) { od_instance_t *instance = client->global->instance; od_route_t *route = client->route; od_rule_storage_t *storage = route->rule->storage; od_target_session_attrs_t tsa = od_tsa_get_effective(client); od_endpoint_attach_candidate_t candidates[OD_STORAGE_MAX_ENDPOINTS]; od_frontend_attach_init_candidates(instance, storage, candidates, tsa, 0 /* prefer localhost */); od_frontend_status_t status = OD_EATTACH; for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_t *endpoint = candidates[i].endpoint; 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 (candidates[i].priority >= 0) { /* * 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); } else { /* connection attempt will fail anyway */ status = OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH; } 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; } od_frontend_status_t od_frontend_attach_and_deploy(od_client_t *client, char *context) { /* attach and maybe connect server */ od_frontend_status_t status; status = od_frontend_attach(client, context, NULL); if (status != OD_OK) { return status; } /* configure server using client parameters */ if (client->rule->maintain_params) { int rc; rc = od_deploy(client, context); if (rc == -1) { return OD_ESERVER_WRITE; } } return OD_OK; } 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: %d 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) { 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 (od_atomic_u64_of(&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 %d 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 %d 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 */ 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); if (machine_timedout()) { /* retry wait to recheck exit condition */ 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, KIWI_BE_EMPTY_QUERY_RESPONSE); 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) { uint32_t name_len; char *name; int rc; rc = kiwi_be_read_bind_stmt_name(data, size, &name, &name_len); if (rc == -1) { return NOT_OK_RESPONSE; } od_log(&instance->logger, ctx, client, client->server, "bind %.*s", name_len, name); return OK_RESPONSE; } /* * machine_sleep with ODYSSEY_CATCHUP_RECHECK_INTERVAL value * will be effitiently just a context switch. */ #define ODYSSEY_CATCHUP_RECHECK_INTERVAL 1 static inline int od_frontend_poll_catchup(od_client_t *client, od_route_t *route, uint32_t timeout, int *lag_out) { od_instance_t *instance = client->global->instance; od_dbg_printf_on_dvl_lvl( 1, "client %s polling replica for catchup with timeout %d\n", client->id.id, timeout); /* * Ensure heartbeet is initialized at least once. * Heartbeat might be 0 after reload\restart. */ int absent_heartbeat_checks = 0; while (route->last_heartbeat == 0) { machine_sleep(ODYSSEY_CATCHUP_RECHECK_INTERVAL); /* add cast to int64_t for correct camparison (int64_t > int and int64_t > uint32_t) */ if ((int64_t)absent_heartbeat_checks++ > (timeout * (int64_t)1000 / ODYSSEY_CATCHUP_RECHECK_INTERVAL)) { od_debug(&instance->logger, "catchup", client, NULL, "No heartbeat for route detected\n"); return OD_ECATCHUP_TIMEOUT; } } for (int check = 1; check <= route->rule->catchup_checks; ++check) { od_dbg_printf_on_dvl_lvl(1, "current cached time %d\n", machine_timeofday_sec()); int lag = machine_timeofday_sec() - route->last_heartbeat; if (lag < 0) { lag = 0; } *lag_out = lag; if ((uint32_t)lag < timeout) { return OD_OK; } od_debug( &instance->logger, "catchup", client, NULL, "client %s%.*s replication %d lag is over catchup timeout %d\n", client->id.id_prefix, OD_ID_LEN, client->id.id, lag, timeout); /* * TBD: Consider configuring `ODYSSEY_CATCHUP_RECHECK_INTERVAL` in * frontend rule. */ if (check < route->rule->catchup_checks) { machine_sleep(ODYSSEY_CATCHUP_RECHECK_INTERVAL); } } 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; assert(route); uint32_t catchup_timeout = route->rule->catchup_timeout; kiwi_var_t *timeout_var = kiwi_vars_get(&client->vars, KIWI_VAR_ODYSSEY_CATCHUP_TIMEOUT); od_frontend_status_t status = OD_OK; 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 */ catchup_timeout = user_catchup_timeout; } else { od_error(&instance->logger, "catchup", client, NULL, "junk after catchup timeout, ignore value"); } } if (catchup_timeout) { od_debug(&instance->logger, "catchup", client, NULL, "checking for lag before doing any actual work"); status = od_frontend_poll_catchup(client, route, catchup_timeout, &client->last_catchup_lag); } return status; } 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; msg = machine_msg_create(sizeof(kiwi_header_t) + size); if (msg == NULL) { return OD_EOOM; } 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) { machine_msg_free(msg); 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; 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: status = od_relay_process_xflush(&client->relay, msg, timeout_ms); break; case KIWI_FE_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"); machine_msg_free(msg); 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)) { od_gerror( "main", client, server, "unexpected message type '%c' for client not in xproto mode", type); status = OD_ECLIENT_PROTOCOL_ERROR; 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 (server->client_pinned) { 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_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); machine_msg_free(msg); if (status != OD_OK) { return status; } 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; 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); 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; 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"); } 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); 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; } assert(err != EINPROGRESS); /* disconnect or other error */ return OD_ESERVER_READ; } return status; } static od_frontend_status_t od_frontend_remote(od_client_t *client) { if (client->route->id.logical_rep || client->route->id.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_CONNECTION_FAILURE, "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_CONNECTION_FAILURE, "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_CONNECTION_FAILURE, "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_CONNECTION_FAILURE, "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_log(&instance->logger, context, client, server, "client disconnected, addr '%s', io error = %s, status %s, working time %lldus", client->peer, od_io_error(&client->io), od_frontend_status_to_str(status), machine_time_us() - client->time_accept); 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; int rc; od_server_t *server = client->server; 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: /* graceful disconnect or kill */ if (instance->config.log_session) { od_log(&instance->logger, context, client, server, "client disconnected (route %s.%s, working time: %lldus)", route->rule->db_name, route->rule->user_name, machine_time_us() - client->time_accept); } if (!client->server) { break; } rc = od_reset(server); if (rc != 1) { /* close backend connection */ od_router_close(router, client); break; } /* push server to router server pool */ od_router_detach(router, client); 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_EATTACH: assert(server == NULL); assert(client->route != NULL); od_frontend_fatal(client, KIWI_CONNECTION_FAILURE, "failed to get remote server connection"); break; case OD_EATTACH_TOO_MANY_CONNECTIONS: assert(server == NULL); 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); break; case OD_EATTACH_TARGET_SESSION_ATTRS_MISMATCH: assert(server == NULL); 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_CONNECTION_FAILURE, "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_CONNECTION_FAILURE, "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_COPY_IN_XPROTO: od_frontend_fatal_detailed( client, KIWI_SYSTEM_ERROR, "Odyssey met CopyInResponse when executing xproto messages, this is not implemented now", "Contact developers, see https://github.com/yandex/odyssey for more", "Copy protocol in xproto met, not implemented now"); od_frontend_on_client_disconnect(status, client, context, 1 /* 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 (%d 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 (%d 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_ESERVER_READ: case OD_ESERVER_WRITE: /* close client connection and close server * connection in case of server errors */ 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_REQ_SYNC: case OD_ATTACH: /* fallthrough */ case OD_DETACH: 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 */ } 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_atomic_u32_dec(&router->clients_routing); return; } /* ensure global client_max limit */ uint32_t clients = od_atomic_u32_inc(&router->clients); if (instance->config.client_max_set && clients >= (uint32_t)instance->config.client_max) { od_frontend_error( client, KIWI_TOO_MANY_CONNECTIONS, "too many tcp connections (global client_max %d)", instance->config.client_max); od_frontend_close(client); od_atomic_u32_dec(&router->clients_routing); return; } /* handle startup */ rc = od_frontend_startup(client); if (rc == -1) { od_frontend_close(client); od_atomic_u32_dec(&router->clients_routing); return; } /* handle cancel request */ if (client->startup.is_cancel) { od_log(&instance->logger, "startup", client, NULL, "cancel request"); 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_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); } od_frontend_close(client); od_atomic_u32_dec(&router->clients_routing); 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_atomic_u32_dec(&router->clients_routing); 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; } 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: 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 (%d), 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 (%d 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) { 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 %lu 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.5.1-rc8/sources/global.c000066400000000000000000000043271517700303500170050ustar00rootroot00000000000000/* * 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; od_atomic_u64_set(&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); } odyssey-1.5.1-rc8/sources/grac_shutdown_worker.c000066400000000000000000000100441517700303500217760ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline void od_grac_shutdown_timeout_killer(void *arg) { od_instance_t *instance = arg; machine_sleep(instance->config.graceful_shutdown_timeout_ms); if (machine_errno() == ECANCELED) { return; } od_error(&instance->logger, "grac-shutdown", NULL, NULL, "graceful shutdown timeout"); exit(1); } void od_grac_shutdown_worker(void *arg) { od_grac_shutdown_worker_arg_t *warg = arg; od_worker_pool_t *worker_pool; od_system_t *system; od_instance_t *instance; od_router_t *router; od_global_t *global; machine_channel_t *channel; system = warg->system; global = system->global; worker_pool = system->global->worker_pool; instance = system->global->instance; router = system->global->router; channel = warg->channel; od_free(warg); int timeout_killer_id = INVALID_COROUTINE_ID; if (instance->config.graceful_shutdown_timeout_ms != 0) { timeout_killer_id = machine_coroutine_create_named( od_grac_shutdown_timeout_killer, instance, "grac_timeout"); if (timeout_killer_id == INVALID_COROUTINE_ID) { od_error(&instance->logger, "grac-shutdown", NULL, NULL, "can't create timeout killer coroutine"); exit(1); } } od_log(&instance->logger, "config", NULL, NULL, "stop to accepting new connections"); #ifdef ODYSSEY_VERSION_GIT od_setproctitlef(&instance->orig_argv_ptr, instance->orig_argv_ptr_len, "odyssey %s (git %s) stop accepting any connections", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT); #else od_setproctitlef(&instance->orig_argv_ptr, instance->orig_argv_ptr_len, "odyssey %s stop accepting any connections", ODYSSEY_VERSION_NUMBER); #endif od_list_t *i; od_list_foreach (&router->servers, i) { od_system_server_t *server; server = od_container_of(i, od_system_server_t, link); atomic_store(&server->closed, true); } od_soft_oom_stop_checker(&global->soft_oom); if (instance->config.host_watcher_enabled) { od_host_watcher_destroy(&global->host_watcher); } od_dbg_printf_on_dvl_lvl(1, "servers closed, errors: %d\n", 0); /* wait for all servers to complete old transactions */ od_list_foreach (&router->servers, i) { #if OD_DEVEL_LVL != OD_RELEASE_MODE od_system_server_t *server; server = od_container_of(i, od_system_server_t, link); #else od_attribute_unused() od_system_server_t *server; server = od_container_of(i, od_system_server_t, link); #endif while (od_atomic_u32_of(&router->clients_routing) || od_atomic_u32_of(&router->clients)) { od_dbg_printf_on_dvl_lvl(1, "waiting for %s\n", server->sid.id); machine_sleep(1000); } od_dbg_printf_on_dvl_lvl(1, "server shutdown ok %s\n", server->sid.id); } /* let storage watchdog's finish */ od_rules_cleanup(&system->global->router->rules); od_worker_pool_shutdown(worker_pool); od_worker_pool_wait_gracefully_shutdown(worker_pool); od_dbg_printf_on_dvl_lvl(1, "shutting down sockets %s\n", ""); if (instance->config.hba_file != NULL) { od_hba_free(global->hba); } machine_stop(system->machine); od_dbg_printf_on_dvl_lvl( 1, "waiting done, sending sigint to own process %d\n", instance->pid.pid); od_system_shutdown(system, instance); if (timeout_killer_id != INVALID_COROUTINE_ID) { int rc = machine_cancel(timeout_killer_id); if (rc != 0) { od_fatal(&instance->logger, "grac-shutdown", NULL, NULL, "failed to cancel timeout killer"); } } machine_msg_t *msg = machine_msg_create(0); if (msg == NULL) { od_fatal(&instance->logger, "system", NULL, NULL, "failed to create a message in grac_shutdown_worker"); } machine_msg_set_type(msg, OD_MSG_GRAC_SHUTDOWN_FINISHED); machine_channel_write(channel, msg); } odyssey-1.5.1-rc8/sources/group.c000066400000000000000000000035741517700303500167040ustar00rootroot00000000000000/* * 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->storage_user) { od_free(group->storage_user); } if (group->storage_db) { od_free(group->storage_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.5.1-rc8/sources/hashmap.c000066400000000000000000000125301517700303500171610ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include od_hashmap_list_item_t *od_hashmap_list_item_create(void) { od_hashmap_list_item_t *list; list = od_malloc(sizeof(od_hashmap_list_item_t)); if (list == NULL) { return NULL; } memset(list, 0, sizeof(od_hashmap_list_item_t)); od_list_init(&list->link); return list; } od_retcode_t od_hashmap_list_item_free(od_hashmap_list_item_t *l) { od_list_unlink(&l->link); od_free(l->key.data); if (l->value.data) { od_free(l->value.data); } od_free(l); return OK_RESPONSE; } static inline od_retcode_t od_hash_bucket_init(od_hashmap_bucket_t *b) { mm_mutex_init(&b->mu); od_list_init(&b->items); return OK_RESPONSE; } static inline od_retcode_t od_hash_bucket_free(od_hashmap_bucket_t *b) { mm_mutex_destroy(&b->mu); return OK_RESPONSE; } od_hashmap_t *od_hashmap_create(size_t sz) { od_hashmap_t *hm; hm = od_malloc(sizeof(od_hashmap_t)); if (hm == NULL) { return NULL; } hm->size = sz; hm->dtor = NULL; hm->buckets = od_malloc(sz * sizeof(od_hashmap_bucket_t)); if (hm->buckets == NULL) { od_free(hm); return NULL; } memset(hm->buckets, 0, sz * sizeof(od_hashmap_bucket_t)); for (size_t i = 0; i < sz; ++i) { if (od_hash_bucket_init(&hm->buckets[i]) == NOT_OK_RESPONSE) { od_free(hm->buckets); od_free(hm); return NULL; } } return hm; } od_hashmap_t *od_hashmap_create_with_dtor(size_t sz, od_hashmap_item_cb_t dtor) { od_hashmap_t *hm = od_hashmap_create(sz); if (hm != NULL) { hm->dtor = dtor; } return hm; } od_retcode_t od_hashmap_free(od_hashmap_t *hm) { for (size_t i = 0; i < hm->size; ++i) { od_hashmap_bucket_t *bucket = &hm->buckets[i]; od_list_t *j, *n; od_list_foreach_safe (&bucket->items, j, n) { od_hashmap_list_item_t *it; it = od_container_of(j, od_hashmap_list_item_t, link); if (hm->dtor) { hm->dtor(it); } od_hashmap_list_item_free(it); } od_hash_bucket_free(bucket); } od_free(hm->buckets); od_free(hm); return OK_RESPONSE; } od_retcode_t od_hashmap_empty(od_hashmap_t *hm) { for (size_t i = 0; i < hm->size; ++i) { od_hashmap_bucket_t *bucket = &hm->buckets[i]; mm_mutex_lock(&bucket->mu, UINT32_MAX); od_list_t *j, *n; od_list_foreach_safe (&bucket->items, j, n) { od_hashmap_list_item_t *it; it = od_container_of(j, od_hashmap_list_item_t, link); if (hm->dtor != NULL) { hm->dtor(it); } od_hashmap_list_item_free(it); } mm_mutex_unlock(&bucket->mu); } return OK_RESPONSE; } static inline od_hashmap_elt_t *od_bucket_search(od_hashmap_bucket_t *b, void *value, size_t value_len) { od_list_t *i; od_list_foreach (&b->items, i) { od_hashmap_list_item_t *item; item = od_container_of(i, od_hashmap_list_item_t, link); if (item->key.len == value_len && memcmp(item->key.data, value, value_len) == 0) { /* find */ return &item->value; } } return NULL; } static inline int od_hashmap_elt_copy(od_hashmap_elt_t *dst, od_hashmap_elt_t *src) { dst->len = src->len; dst->data = od_malloc(src->len * sizeof(char)); if (dst->data == NULL) { return -1; } memcpy(dst->data, src->data, src->len); return 0; } int od_hashmap_insert(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key, od_hashmap_elt_t **value) { size_t bucket_index = keyhash % hm->size; od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index]; mm_mutex_lock(&bucket->mu, UINT32_MAX); od_hashmap_elt_t *ptr = od_bucket_search(bucket, key->data, key->len); int ret = 1; if (ptr == NULL) { od_hashmap_list_item_t *it = od_hashmap_list_item_create(); if (it == NULL) { /* oom or other error */ mm_mutex_unlock(&bucket->mu); return -1; } od_hashmap_elt_copy(&it->key, key); od_hashmap_elt_copy(&it->value, *value); od_list_append(&bucket->items, &it->link); ret = 0; } else { /* element already exists, * copy *value content to ptr data * free previous value */ od_free(ptr->data); od_hashmap_elt_copy(ptr, *value); *value = ptr; } mm_mutex_unlock(&bucket->mu); return ret; } od_hashmap_elt_t *od_hashmap_find(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key) { size_t bucket_index = keyhash % hm->size; od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index]; mm_mutex_lock(&bucket->mu, UINT32_MAX); od_hashmap_elt_t *ptr = od_bucket_search(bucket, key->data, key->len); mm_mutex_unlock(&bucket->mu); return ptr; } od_hashmap_elt_t *od_hashmap_lock_key(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key) { size_t bucket_index = keyhash % hm->size; od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index]; mm_mutex_lock(&bucket->mu, UINT32_MAX); od_hashmap_elt_t *ptr = od_bucket_search(bucket, key->data, key->len); if (ptr == NULL) { od_hashmap_list_item_t *it = od_hashmap_list_item_create(); if (it == NULL) { /* oom or other error */ return NULL; } od_hashmap_elt_copy(&it->key, key); od_list_append(&bucket->items, &it->link); return &it->value; } /* element already exists, simpty return locked key */ return ptr; } int od_hashmap_unlock_key(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key) { (void)key; size_t bucket_index = keyhash % hm->size; od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index]; mm_mutex_unlock(&bucket->mu); return 0 /* OK */; } odyssey-1.5.1-rc8/sources/hba.c000066400000000000000000000056711517700303500163020ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include void od_hba_init(od_hba_t *hba) { pthread_mutex_init(&hba->lock, NULL); od_hba_rules_init(&hba->rules); } void od_hba_free(od_hba_t *hba) { od_hba_rules_free(&hba->rules); pthread_mutex_destroy(&hba->lock); } void od_hba_lock(od_hba_t *hba) { pthread_mutex_lock(&hba->lock); } void od_hba_unlock(od_hba_t *hba) { pthread_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; } 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->rule->db_name, &rule->database, client->rule->user_name)) { continue; } if (!od_hba_validate_name(client->rule->user_name, &rule->user, client->rule->db_name)) { continue; } rc = rule->auth_method == OD_CONFIG_HBA_ALLOW ? OK_RESPONSE : NOT_OK_RESPONSE; return rc; } return NOT_OK_RESPONSE; } odyssey-1.5.1-rc8/sources/hba_reader.c000066400000000000000000000200701517700303500176120ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include enum { OD_LLOCAL, OD_LHOST, OD_LHOSTSSL, OD_LHOSTNOSSL, OD_LALL, OD_LSAMEUSER, OD_LALLOW, OD_LDENY, }; static od_keyword_t od_hba_keywords[] = { /* connection types */ od_keyword("local", OD_LLOCAL), od_keyword("host", OD_LHOST), od_keyword("hostssl", OD_LHOSTSSL), od_keyword("hostnossl", OD_LHOSTNOSSL), /* db/user */ od_keyword("all", OD_LALL), od_keyword("sameuser", OD_LSAMEUSER), /* auth type */ od_keyword("allow", OD_LALLOW), od_keyword("trust", OD_LALLOW), od_keyword("deny", OD_LDENY), od_keyword("reject", OD_LDENY), { 0, 0, 0 }, }; static void od_hba_reader_error(od_config_reader_t *reader, char *msg) { od_errorf(reader->error, "%s:%d %s", reader->config_file, reader->parser.line, msg); } static int od_hba_parser_next(od_parser_t *parser, od_token_t *token) { /* try to use backlog */ if (parser->backlog_count > 0) { *token = parser->backlog[parser->backlog_count - 1]; parser->backlog_count--; return token->type; } /* skip white spaces and comments */ for (;;) { while (parser->pos < parser->end && isspace(*parser->pos)) { if (*parser->pos == '\n') { parser->line++; } parser->pos++; } if (od_unlikely(parser->pos == parser->end)) { token->type = OD_PARSER_EOF; return token->type; } if (*parser->pos != '#') { break; } while (parser->pos < parser->end && *parser->pos != '\n') { parser->pos++; } if (parser->pos == parser->end) { token->type = OD_PARSER_EOF; return token->type; } parser->line++; } /* symbols */ if (*parser->pos != '\"' && ispunct(*parser->pos)) { token->type = OD_PARSER_SYMBOL; token->line = parser->line; token->value.num = *parser->pos; parser->pos++; return token->type; } if (isalnum(*parser->pos)) { token->type = OD_PARSER_KEYWORD; token->line = parser->line; token->value.string.pointer = parser->pos; while (parser->pos < parser->end && *parser->pos != ',' && (isalnum(*parser->pos) || ispunct(*parser->pos))) { parser->pos++; } token->value.string.size = parser->pos - token->value.string.pointer; return token->type; } if (*parser->pos == '\"') { token->type = OD_PARSER_STRING; token->line = parser->line; parser->pos++; token->value.string.pointer = parser->pos; while (parser->pos < parser->end && *parser->pos != '\"') { if (*parser->pos == '\n') { token->type = OD_PARSER_ERROR; return token->type; } parser->pos++; } if (od_unlikely(parser->pos == parser->end)) { token->type = OD_PARSER_ERROR; return token->type; } token->value.string.size = parser->pos - token->value.string.pointer; parser->pos++; return token->type; } /* error */ token->type = OD_PARSER_ERROR; token->line = parser->line; return token->type; } static int od_hba_reader_match_string(od_token_t token, char **value) { char *copy = od_malloc(token.value.string.size + 1); if (copy == NULL) { return NOT_OK_RESPONSE; } memcpy(copy, token.value.string.pointer, token.value.string.size); copy[token.value.string.size] = 0; if (*value) { od_free(*value); } *value = copy; return OK_RESPONSE; } static int od_hba_reader_value(od_config_reader_t *reader, void **dest) { od_token_t token; int rc; char *string_value = NULL; rc = od_hba_parser_next(&reader->parser, &token); switch (rc) { case OD_PARSER_EOF: return rc; case OD_PARSER_KEYWORD: { od_keyword_t *match; match = od_keyword_match(od_hba_keywords, &token); if (match) { *dest = match; return OD_PARSER_KEYWORD; } if (od_hba_reader_match_string(token, &string_value) == OK_RESPONSE) { *dest = string_value; return OD_PARSER_STRING; } od_hba_reader_error(reader, "unable to read string"); return -1; } case OD_PARSER_STRING: if (od_hba_reader_match_string(token, &string_value) == OK_RESPONSE) { *dest = string_value; return OD_PARSER_STRING; } od_hba_reader_error(reader, "unable to read string"); return -1; default: od_hba_reader_error(reader, "expected string or keyword"); return -1; } } static int od_hba_reader_name(od_config_reader_t *reader, struct od_hba_rule_name *name, bool is_db) { od_keyword_t *keyword = NULL; int rc; void *value = NULL; od_token_t token; for (;;) { rc = od_hba_reader_value(reader, &value); switch (rc) { case OD_PARSER_STRING: { struct od_hba_rule_name_item *item = od_hba_rule_name_item_add(name); item->value = (char *)value; break; } case OD_PARSER_KEYWORD: keyword = (od_keyword_t *)value; switch (keyword->id) { case OD_LALL: name->flags |= OD_HBA_NAME_ALL; break; case OD_LSAMEUSER: if (is_db) { name->flags |= OD_HBA_NAME_SAMEUSER; } } break; default: od_hba_reader_error(reader, "expected name or keyword"); return -1; } rc = od_hba_parser_next(&reader->parser, &token); if (rc == OD_PARSER_SYMBOL && token.value.num == ',') { continue; } od_parser_push(&reader->parser, &token); return 0; } } int od_hba_reader_parse(od_config_reader_t *reader) { od_hba_rule_t *hba = NULL; for (;;) { hba = od_hba_rule_create(); if (hba == NULL) { od_hba_reader_error(reader, "memory allocation error"); return -1; } /* connection type */ od_keyword_t *keyword = NULL; void *connection_type = NULL; od_hba_rule_conn_type_t conn_type; int rc; rc = od_hba_reader_value(reader, &connection_type); if (rc == OD_PARSER_EOF) { od_hba_rule_free(hba); return 0; } if (rc != OD_PARSER_KEYWORD) { od_hba_reader_error(reader, "invalid connection type"); goto error; } keyword = (od_keyword_t *)connection_type; switch (keyword->id) { case OD_LLOCAL: conn_type = OD_CONFIG_HBA_LOCAL; break; case OD_LHOST: conn_type = OD_CONFIG_HBA_HOST; break; case OD_LHOSTSSL: conn_type = OD_CONFIG_HBA_HOSTSSL; break; case OD_LHOSTNOSSL: conn_type = OD_CONFIG_HBA_HOSTNOSSL; break; default: od_hba_reader_error(reader, "invalid connection type"); goto error; } hba->connection_type = conn_type; /* db & user name */ if (od_hba_reader_name(reader, &hba->database, true) != 0) { goto error; } if (od_hba_reader_name(reader, &hba->user, false) != 0) { goto error; } if (conn_type != OD_CONFIG_HBA_LOCAL) { void *address = NULL; char *mask = NULL; /* ip address */ rc = od_hba_reader_value(reader, &address); if (rc != OD_PARSER_STRING) { od_hba_reader_error(reader, "expected IP address"); goto error; } mask = strchr(address, '/'); if (mask) { *mask++ = 0; } if (od_address_read(&hba->address_range.addr, address) == NOT_OK_RESPONSE) { od_hba_reader_error(reader, "invalid IP address"); goto error; } /* network mask */ if (mask) { if (od_address_range_read_prefix( &hba->address_range, mask) == -1) { od_hba_reader_error( reader, "invalid network prefix length"); goto error; } } else { rc = od_hba_reader_value(reader, &address); if (rc != OD_PARSER_STRING) { od_hba_reader_error( reader, "expected network mask"); goto error; } if (od_address_read(&hba->address_range.mask, address) == -1) { od_hba_reader_error( reader, "invalid network mask"); goto error; } } if (address) { free(address); } } /* auth method */ void *auth_method = NULL; rc = od_hba_reader_value(reader, &auth_method); if (rc != OD_PARSER_KEYWORD) { od_hba_reader_error(reader, "expected auth method"); goto error; } keyword = (od_keyword_t *)auth_method; switch (keyword->id) { case OD_LALLOW: hba->auth_method = OD_CONFIG_HBA_ALLOW; break; case OD_LDENY: hba->auth_method = OD_CONFIG_HBA_DENY; break; default: od_hba_reader_error( reader, "invalid auth method: only allow/deny or trust/reject is now supported"); goto error; } od_hba_rules_add(reader->hba_rules, hba); } error: od_hba_rule_free(hba); return -1; } odyssey-1.5.1-rc8/sources/hba_rule.c000066400000000000000000000033111517700303500173160ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/host_watcher.c000066400000000000000000000116341517700303500202360ustar00rootroot00000000000000#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) { pthread_spin_lock(&hw->lock); hw_update_cpu_stat(hw, cpu); hw_update_mem_stat(hw, mem); pthread_spin_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 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", &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: %ld kB", &mem_available) == 1) { continue; } if (sscanf(line, "MemTotal: %ld 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)); pthread_spin_init(&hw->lock, PTHREAD_PROCESS_PRIVATE); hw->hz = sysconf(_SC_CLK_TCK); if (hw->hz == -1) { return -1; } hw->stop_flag = machine_wait_flag_create(); if (hw->stop_flag == NULL) { pthread_spin_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); pthread_spin_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); pthread_spin_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) { pthread_spin_lock(&hw->lock); *cpu = get_cpu_util(hw); *mem = get_mem_util(hw); pthread_spin_unlock(&hw->lock); } odyssey-1.5.1-rc8/sources/include/000077500000000000000000000000001517700303500170165ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/address.h000066400000000000000000000036101517700303500206140ustar00rootroot00000000000000#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 *); int od_address_hostname_validate(od_config_reader_t *, char *); odyssey-1.5.1-rc8/sources/include/affinity.h000066400000000000000000000044551517700303500210100ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #ifndef OD_AFFINITY_MAX_CPUS #define OD_AFFINITY_MAX_CPUS CPU_SETSIZE #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); /* * 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); 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.5.1-rc8/sources/include/atomic.h000066400000000000000000000043151517700303500204460ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef volatile uint32_t od_atomic_u32_t; typedef volatile uint64_t od_atomic_u64_t; static inline uint32_t od_atomic_u32_of(od_atomic_u32_t *atomic) { return __sync_fetch_and_add(atomic, 0); } static inline uint32_t od_atomic_u32_inc(od_atomic_u32_t *atomic) { return __sync_fetch_and_add(atomic, 1); } static inline uint32_t od_atomic_u32_dec(od_atomic_u32_t *atomic) { return __sync_fetch_and_sub(atomic, 1); } static inline uint32_t od_atomic_u32_add(od_atomic_u32_t *atomic, uint32_t value) { return __sync_add_and_fetch(atomic, value); } static inline uint32_t od_atomic_u32_sub(od_atomic_u32_t *atomic, uint32_t value) { return __sync_sub_and_fetch(atomic, value); } static inline uint32_t od_atomic_u32_or(od_atomic_u32_t *atomic, uint32_t value) { return __sync_or_and_fetch(atomic, value); } static inline uint32_t od_atomic_u32_xor(od_atomic_u32_t *atomic, uint32_t value) { return __sync_xor_and_fetch(atomic, value); } static inline uint64_t od_atomic_u64_of(od_atomic_u64_t *atomic) { return __sync_fetch_and_add(atomic, 0); } static inline uint64_t od_atomic_u64_inc(od_atomic_u64_t *atomic) { return __sync_fetch_and_add(atomic, 1); } static inline uint64_t od_atomic_u64_dec(od_atomic_u64_t *atomic) { return __sync_fetch_and_sub(atomic, 1); } static inline uint64_t od_atomic_u64_add(od_atomic_u64_t *atomic, uint64_t value) { return __sync_add_and_fetch(atomic, value); } static inline uint64_t od_atomic_u64_sub(od_atomic_u64_t *atomic, uint64_t value) { return __sync_sub_and_fetch(atomic, value); } static inline uint32_t od_atomic_u32_cas(od_atomic_u32_t *atomic, uint32_t compValue, uint32_t exchValue) { return __sync_val_compare_and_swap(atomic, compValue, exchValue); } static inline uint64_t od_atomic_u64_cas(od_atomic_u64_t *atomic, uint64_t compValue, uint64_t exchValue) { return __sync_val_compare_and_swap(atomic, compValue, exchValue); } static inline void od_atomic_u64_set(od_atomic_u64_t *atomic, uint64_t newValue) { for (;;) { uint64_t oldValue = od_atomic_u64_of(atomic); if (__sync_bool_compare_and_swap(atomic, oldValue, newValue)) { break; } } } odyssey-1.5.1-rc8/sources/include/attach.h000066400000000000000000000003261517700303500204340ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int od_attach_extended(od_instance_t *instance, char *context, od_router_t *router, od_client_t *client); odyssey-1.5.1-rc8/sources/include/attribute.h000066400000000000000000000004571517700303500212000ustar00rootroot00000000000000#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.5.1-rc8/sources/include/auth.h000066400000000000000000000003141517700303500201260ustar00rootroot00000000000000#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.5.1-rc8/sources/include/auth_query.h000066400000000000000000000004121517700303500213520ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #define ODYSSEY_AUTH_QUERY_MAX_PASSWORD_LEN 4096 od_hashmap_t *od_auth_query_create_cache(size_t sz); int od_auth_query(od_client_t *, char *); odyssey-1.5.1-rc8/sources/include/backend.h000066400000000000000000000031031517700303500205530ustar00rootroot00000000000000#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 *); int od_backend_check_tsa(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 *); /* need perform startup after this */ int od_backend_connect_to(od_server_t *, char *, const od_address_t *, od_tls_opts_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, char *query, char *param, int len); 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 *); odyssey-1.5.1-rc8/sources/include/build.h.cmake000066400000000000000000000014551517700303500213520ustar00rootroot00000000000000#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_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.5.1-rc8/sources/include/cancel.h000066400000000000000000000004101517700303500204070ustar00rootroot00000000000000#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.5.1-rc8/sources/include/client.h000066400000000000000000000052531517700303500204520ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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_config_listen_t *config_listen; uint64_t time_accept; uint64_t time_setup; uint64_t time_last_active; 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; /* 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 */ od_atomic_u64_t killed; /* 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 int last_catchup_lag; /* external_id for logging additional info about client authneticated with external auth */ char *external_id; }; 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; } 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) { od_atomic_u64_set(&client->killed, 1UL); } uint32_t od_client_login_timeout(const od_client_t *client); odyssey-1.5.1-rc8/sources/include/client_pool.h000066400000000000000000000055741517700303500215110ustar00rootroot00000000000000#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: 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: 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; } odyssey-1.5.1-rc8/sources/include/common/000077500000000000000000000000001517700303500203065ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/common/base64.h000066400000000000000000000010551517700303500215440ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/include/common/cryptohash.h000066400000000000000000000022771517700303500226530ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/fe_memutils.h000066400000000000000000000061541517700303500227760ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/include/common/hmac.h000066400000000000000000000017131517700303500213710ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/md5.h000066400000000000000000000022451517700303500211470ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/md5_int.h000066400000000000000000000055171517700303500220260ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/saslprep.h000066400000000000000000000015201517700303500223060ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/scram-common.h000066400000000000000000000046111517700303500230540ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/sha1.h000066400000000000000000000011031517700303500213060ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/sha2.h000066400000000000000000000021631517700303500213160ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/string.h000066400000000000000000000030511517700303500217640ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/include/common/unicode_east_asian_fw_table.h000066400000000000000000000054211517700303500261410ustar00rootroot00000000000000/* 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.5.1-rc8/sources/include/common/unicode_nonspacing_table.h000066400000000000000000000164521517700303500255030ustar00rootroot00000000000000/* 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.5.1-rc8/sources/include/common/unicode_norm.h000066400000000000000000000016741517700303500231500ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common/unicode_norm_table.h000066400000000000000000013333211517700303500243150ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/common_const.h000066400000000000000000000002431517700303500216640ustar00rootroot00000000000000#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.5.1-rc8/sources/include/compression.h000066400000000000000000000003241517700303500215270ustar00rootroot00000000000000#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.5.1-rc8/sources/include/config.h000066400000000000000000000057251517700303500204450ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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; }; 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; }; 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 server_login_retry; int reserve_session_server_connection; /* */ int cache_coroutine; int cache_msg_gc_size; int coroutine_stack_size; 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 virtual_processing; /* enables some cases for full-virtual query processing */ char availability_zone[OD_MAX_AVAILABILITY_ZONE_LENGTH]; int max_sigterms_to_die; int host_watcher_enabled; od_config_soft_oom_t soft_oom; int smart_search_path_enquoting; od_affinity_config_t *cpu_affinity; }; 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 *); odyssey-1.5.1-rc8/sources/include/config_common.h000066400000000000000000000007261517700303500220110ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include struct od_config_reader { od_parser_t parser; od_config_t *config; od_global_t *global; od_rules_t *rules; od_list_t shared_pools; od_error_t *error; char *config_file; od_hba_rules_t *hba_rules; char *data; int data_size; regex_t rfc952_hostname_regex; }; odyssey-1.5.1-rc8/sources/include/config_reader.h000066400000000000000000000024571517700303500217660ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include extern int od_config_reader_import(od_config_t *, od_rules_t *, od_error_t *, od_extension_t *, od_global_t *, od_hba_rules_t *, char *); #define OD_READER_ERROR_MAX_LEN 1 << 8 static inline void od_config_reader_error(od_config_reader_t *reader, od_token_t *token, char *fmt, ...) { char msg[OD_READER_ERROR_MAX_LEN]; va_list args; va_start(args, fmt); od_vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); int line = reader->parser.line; if (token) { line = token->line; } od_errorf(reader->error, "%s:%d %s", reader->config_file, line, msg); } static inline bool od_config_reader_symbol(od_config_reader_t *reader, char symbol) { od_token_t token; int rc; rc = od_parser_next(&reader->parser, &token); if (rc != OD_PARSER_SYMBOL) { goto error; } if (token.value.num != (int64_t)symbol) { goto error; } return true; error: od_parser_push(&reader->parser, &token); od_config_reader_error(reader, &token, "expected '%c'", symbol); return false; } bool od_config_reader_keyword(od_config_reader_t *reader, od_keyword_t *keyword); odyssey-1.5.1-rc8/sources/include/console.h000066400000000000000000000003211517700303500206250ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_console_query(od_client_t *, machine_msg_t *, char *, uint32_t); odyssey-1.5.1-rc8/sources/include/counter.h000066400000000000000000000011501517700303500206430ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #define OD_COUNTER_MAX_POSSIBLE_VALUE 1000UL typedef struct od_counter { size_t size; od_atomic_u64_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.5.1-rc8/sources/include/cron.h000066400000000000000000000011201517700303500201220ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #ifdef PROM_FOUND #include #endif struct od_cron { uint64_t stat_time_us; od_global_t *global; od_atomic_u64_t startup_errors; #ifdef PROM_FOUND od_prom_metrics_t *metrics; #endif machine_wait_flag_t *can_be_freed; atomic_int 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); odyssey-1.5.1-rc8/sources/include/daemon.h000066400000000000000000000001471517700303500204340ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ int od_daemonize(void); odyssey-1.5.1-rc8/sources/include/debugprintf.h000066400000000000000000000011531517700303500215000ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include void od_dbg_printf(char *fmt, ...); #define OD_RELEASE_MODE -1 #ifndef OD_DEVEL_LVL /* set "release" mode by default */ #define OD_DEVEL_LVL OD_RELEASE_MODE #endif #if OD_DEVEL_LVL == OD_RELEASE_MODE #define od_dbg_printf_on_dvl_lvl(debug_lvl, fmt, ...) /* zero cost debug print on release mode */ #else #define od_dbg_printf_on_dvl_lvl(debug_lvl, fmt, ...) \ \ if (OD_DEVEL_LVL >= debug_lvl) { \ od_dbg_printf(fmt, __VA_ARGS__); \ } #endif odyssey-1.5.1-rc8/sources/include/deploy.h000066400000000000000000000002431517700303500204620ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_deploy(od_client_t *, char *); odyssey-1.5.1-rc8/sources/include/dns.h000066400000000000000000000005411517700303500177530ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include int od_getsockaddrname(struct sockaddr *, char *, int, int, int); int od_getaddrname(struct addrinfo *, 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.5.1-rc8/sources/include/ejection.h000066400000000000000000000011511517700303500207650ustar00rootroot00000000000000#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? */ pthread_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.5.1-rc8/sources/include/err_logger.h000066400000000000000000000015371517700303500213240ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #define DEFAULT_ERROR_INTERVAL_NUMBER (5 * 60) struct od_error_logger { size_t intervals_cnt; 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.5.1-rc8/sources/include/extension.h000066400000000000000000000020001517700303500211730ustar00rootroot00000000000000#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.5.1-rc8/sources/include/external_auth.h000066400000000000000000000005701517700303500220340ustar00rootroot00000000000000#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.5.1-rc8/sources/include/frontend.h000066400000000000000000000062371517700303500210160ustar00rootroot00000000000000#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; msg_len = od_snprintf(msg, sizeof(msg), "odyssey: %s%.*s: ", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); 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; msg_len = od_snprintf(msg, sizeof(msg), "odyssey: %s%.*s: ", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); 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; msg_len = od_snprintf(msg, sizeof(msg), "odyssey: %s%.*s: ", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); 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_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.5.1-rc8/sources/include/global.h000066400000000000000000000040471517700303500204340ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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; od_atomic_u64_t pause; mm_wait_list_t *resume_waiters; }; 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_free(global); od_global_set(NULL); } static inline uint64_t od_global_is_paused(od_global_t *global) { return od_atomic_u64_of(&global->pause); } static inline void od_global_pause(od_global_t *global) { od_atomic_u64_set(&global->pause, 1ULL); } static inline void od_global_resume(od_global_t *global) { od_atomic_u64_set(&global->pause, 0ULL); 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); odyssey-1.5.1-rc8/sources/include/grac_shutdown_worker.h000066400000000000000000000004301517700303500234240ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include typedef struct { od_system_t *system; machine_channel_t *channel; } od_grac_shutdown_worker_arg_t; void od_grac_shutdown_worker(void *arg); odyssey-1.5.1-rc8/sources/include/group.h000066400000000000000000000014041517700303500203220ustar00rootroot00000000000000#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 *storage_user; char *storage_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.5.1-rc8/sources/include/hashmap.h000066400000000000000000000037121517700303500206130ustar00rootroot00000000000000#pragma once #include #include #include #include typedef struct od_hashmap_list_item od_hashmap_list_item_t; /* * void * data should have following fmt: key value */ /* * TODO: make hashmap more simple in impl and usage * for ex.: remove double pointer to buckets * and do not require hashing at user-side */ /* header, first keylen bytes is key, other is value */ typedef struct { void *data; size_t len; } od_hashmap_elt_t; struct od_hashmap_list_item { od_hashmap_elt_t key; od_hashmap_elt_t value; od_list_t link; }; extern od_hashmap_list_item_t *od_hashmap_list_item_create(void); extern od_retcode_t od_hashmap_list_item_free(od_hashmap_list_item_t *l); typedef struct od_hashmap_bucket { od_list_t items; mm_mutex_t mu; } od_hashmap_bucket_t; typedef struct od_hashmap od_hashmap_t; typedef void (*od_hashmap_item_cb_t)(od_hashmap_list_item_t *item); struct od_hashmap { size_t size; od_hashmap_item_cb_t dtor; od_hashmap_bucket_t *buckets; }; extern od_hashmap_t *od_hashmap_create(size_t sz); extern od_hashmap_t *od_hashmap_create_with_dtor(size_t sz, od_hashmap_item_cb_t dtor); extern od_retcode_t od_hashmap_free(od_hashmap_t *hm); od_hashmap_elt_t *od_hashmap_find(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key); /* This function insert new key into hashmap * If hashmap already contains value associated with key, * it will be rewritten. */ int od_hashmap_insert(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key, od_hashmap_elt_t **value); /* LOCK-UNLOCK API */ /* given key and its * keyhash (murmurhash etc) return pointer * to hashmap mutex-locked value pointer */ od_hashmap_elt_t *od_hashmap_lock_key(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key); int od_hashmap_unlock_key(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key); /* clear hashmap */ od_retcode_t od_hashmap_empty(od_hashmap_t *hm); odyssey-1.5.1-rc8/sources/include/hba.h000066400000000000000000000005431517700303500177230ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include struct od_hba { pthread_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); int od_hba_process(od_client_t *client); odyssey-1.5.1-rc8/sources/include/hba_reader.h000066400000000000000000000001271517700303500212430ustar00rootroot00000000000000#pragma once #include int od_hba_reader_parse(od_config_reader_t *reader); odyssey-1.5.1-rc8/sources/include/hba_rule.h000066400000000000000000000023641517700303500207550ustar00rootroot00000000000000#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.5.1-rc8/sources/include/host_watcher.h000066400000000000000000000012371517700303500216640ustar00rootroot00000000000000#pragma once #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; pthread_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.5.1-rc8/sources/include/id.h000066400000000000000000000021741517700303500175670ustar00rootroot00000000000000#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 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.5.1-rc8/sources/include/instance.h000066400000000000000000000017771517700303500210070ustar00rootroot00000000000000#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; struct { int argc; char **argv; char **envp; } cmdline; mm_hashmap_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); mm_hashmap_t *od_instance_get_pstmts_map(od_instance_t *instance); odyssey-1.5.1-rc8/sources/include/internal_client.h000066400000000000000000000024421517700303500223430ustar00rootroot00000000000000#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.5.1-rc8/sources/include/io.h000066400000000000000000000112311517700303500175740ustar00rootroot00000000000000#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); 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 machine_msg_t *od_read(od_io_t *io, uint32_t time_ms) { 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; rc = kiwi_validate_header((char *)&header, sizeof(header), &size); 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); } 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); } int od_io_write_raw(od_io_t *io, const void *buf, size_t size, size_t *processed, uint32_t timeout_ms); /* breaks iovec arg */ int od_io_writev(od_io_t *io, struct iovec *iov, int iovcnt, uint32_t timeout_ms); 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.5.1-rc8/sources/include/kiwi/000077500000000000000000000000001517700303500177615ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/kiwi/be_read.h000066400000000000000000000265261517700303500215260ustar00rootroot00000000000000#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; 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->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)) { kiwi_parse_options_and_update_vars(vars, value, value_size); } 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 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(2, 0) 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 -1; } uint32_t version; rc = kiwi_read32(&version, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } su->unsupported_request = 0; switch (version) { /* StartupMessage */ case PG_PROTOCOL_LATEST: su->is_cancel = 0; rc = kiwi_be_read_options(su, pos, pos_size, vars); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* CancelRequest */ case CANCEL_REQUEST_CODE: su->is_cancel = 1; rc = kiwi_read32(&su->key.key_pid, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } rc = kiwi_read32(&su->key.key, &pos, &pos_size); if (kiwi_unlikely(rc == -1)) { return -1; } break; /* SSLRequest */ case NEGOTIATE_SSL_CODE: su->is_ssl_request = 1; break; /* GSSRequest */ case NEGOTIATE_GSS_CODE: /* V2 protocol startup */ case PG_PROTOCOL_EARLIEST: su->unsupported_request = 1; break; default: return -1; } return 0; } 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 > KIWI_LONG_MESSAGE_SIZE) { 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; } *query = kiwi_header_data(header); *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; 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_stmt_name(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_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 */ *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_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.5.1-rc8/sources/include/kiwi/be_write.h000066400000000000000000000463571517700303500217510ustar00rootroot00000000000000#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_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 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_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; } odyssey-1.5.1-rc8/sources/include/kiwi/error_codes.h000066400000000000000000000317431517700303500224500ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/fe_read.h000066400000000000000000000112371517700303500215230ustar00rootroot00000000000000#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: /* SCRAM-SHA-256 is the only implemented SASL mechanism in * PostgreSQL, at the moment */ if (strcmp(pos, "SCRAM-SHA-256") != 0) { 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.5.1-rc8/sources/include/kiwi/fe_write.h000066400000000000000000000271671517700303500217530ustar00rootroot00000000000000#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, 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; } 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, 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, 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.5.1-rc8/sources/include/kiwi/header.h000066400000000000000000000077261517700303500213760ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/io.h000066400000000000000000000117651517700303500205530ustar00rootroot00000000000000#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; } #define KIWI_LONG_MESSAGE_SIZE 640 * 1024 * 1024 /* Outght to be enough */ 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 >= KIWI_LONG_MESSAGE_SIZE)) { return -1; } return 0; } KIWI_API static inline int kiwi_validate_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; } /* is small packet */ if (kiwi_likely(*size < KIWI_LONG_MESSAGE_SIZE)) { return 0; } /* * Lists the backend and frontend message types that could be "long" (more * than a couple of kilobytes). */ switch (header->type) { /* backend */ case KIWI_BE_ROW_DESCRIPTION: case KIWI_BE_DATA_ROW: case KIWI_BE_COPY_DATA: case KIWI_BE_FUNCTION_CALL_RESPONSE: case KIWI_BE_ERROR_RESPONSE: case KIWI_BE_NOTICE_RESPONSE: case KIWI_BE_NOTIFICATION_RESPONSE: case KIWI_BE_PARAMETER_DESCRIPTION: /* frontend */ case KIWI_FE_BIND: case KIWI_FE_PARSE: case KIWI_FE_QUERY: /* KIWI_FE_COPY_DATA has same type as BE_COPY_DATA */ return 0; } return -1; } odyssey-1.5.1-rc8/sources/include/kiwi/key.h000066400000000000000000000005731517700303500207270ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/kiwi.h000066400000000000000000000013371517700303500211010ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/macro.h000066400000000000000000000004761517700303500212420ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/md5.h000066400000000000000000000006211517700303500206160ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/options.h000066400000000000000000000002761517700303500216320ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/param.h000066400000000000000000000056531517700303500212430ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/param_lock.h000066400000000000000000000022201517700303500222360ustar00rootroot00000000000000#pragma once /* * kiwi. * * postgreSQL protocol interaction library. */ typedef struct kiwi_params_lock kiwi_params_lock_t; struct kiwi_params_lock { pthread_mutex_t lock; kiwi_params_t params; }; static inline void kiwi_params_lock_init(kiwi_params_lock_t *pl) { pthread_mutex_init(&pl->lock, NULL); kiwi_params_init(&pl->params); } static inline void kiwi_params_lock_free(kiwi_params_lock_t *pl) { pthread_mutex_destroy(&pl->lock); kiwi_params_free(&pl->params); } static inline int kiwi_params_lock_count(kiwi_params_lock_t *pl) { pthread_mutex_lock(&pl->lock); int rc = pl->params.count; pthread_mutex_unlock(&pl->lock); return rc; } static inline int kiwi_params_lock_copy(kiwi_params_lock_t *pl, kiwi_params_t *dest) { pthread_mutex_lock(&pl->lock); int rc; rc = kiwi_params_copy(dest, &pl->params); pthread_mutex_unlock(&pl->lock); return rc; } static inline int kiwi_params_lock_set_once(kiwi_params_lock_t *pl, kiwi_params_t *params) { pthread_mutex_lock(&pl->lock); if (pl->params.count > 0) { pthread_mutex_unlock(&pl->lock); return 0; } pl->params = *params; pthread_mutex_unlock(&pl->lock); return 1; } odyssey-1.5.1-rc8/sources/include/kiwi/password.h000066400000000000000000000044101517700303500217730ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/sasl.h000066400000000000000000000002101517700303500210650ustar00rootroot00000000000000#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.5.1-rc8/sources/include/kiwi/var.h000066400000000000000000000201631517700303500207240ustar00rootroot00000000000000#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_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, /* 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_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")); /* XXX: todo - also accept aliases */ kiwi_var_init(&vars->vars[KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS], "target_session_attrs", sizeof("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.5.1-rc8/sources/include/ldap_endpoint.h000066400000000000000000000051511517700303500220110ustar00rootroot00000000000000#pragma once #include #include #include #include typedef struct { pthread_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; #if USE_POOL void *ldap_search_pool; void *ldap_auth_pool; #endif machine_channel_t *wait_bus; 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; static inline void od_ldap_endpoint_lock(od_ldap_endpoint_t *le) { pthread_mutex_lock(&le->lock); } static inline void od_ldap_endpoint_unlock(od_ldap_endpoint_t *le) { pthread_mutex_unlock(&le->lock); } static inline int od_ldap_endpoint_wait(od_ldap_endpoint_t *le, uint32_t time_ms) { machine_msg_t *msg; msg = machine_channel_read(le->wait_bus, time_ms); if (msg) { machine_msg_free(msg); return 0; } return -1; } static inline int od_ldap_endpoint_signal(od_ldap_endpoint_t *le) { machine_msg_t *msg; msg = machine_msg_create(0); if (msg == NULL) { return -1; } machine_channel_write(le->wait_bus, msg); return 0; } 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.5.1-rc8/sources/include/list.h000066400000000000000000000030401517700303500201370ustar00rootroot00000000000000#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.5.1-rc8/sources/include/logger.h000066400000000000000000000160541517700303500204540ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #define OD_LOGLINE_MAXLEN 1024 #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_FORMAT_TEXT, OD_LOGGER_FORMAT_JSON } od_logger_format_type_t; typedef struct { od_logger_level_t level; od_list_t link; 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; int fd; atomic_uint_fast64_t loaded; int64_t machine; od_logger_slot_t *slots; mm_queue_t tasks; od_list_t free_slots; size_t free_slots_count; pthread_spinlock_t free_slots_lock; 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; } static inline void od_logger_set_format(od_logger_t *logger, char *format) { logger->format = format; logger->format_len = strlen(format); /* Detect JSON format */ if (strcasestr(format, "json") != NULL) { logger->format_type = OD_LOGGER_FORMAT_JSON; } else { logger->format_type = OD_LOGGER_FORMAT_TEXT; } } 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 *, char *); extern int od_logger_reopen(od_logger_t *, char *); 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); void od_logger_wait_finish(od_logger_t *); 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.5.1-rc8/sources/include/machinarium/000077500000000000000000000000001517700303500213135ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/machinarium/bind.h000066400000000000000000000002351517700303500224000ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #define MM_BINDWITH_SO_REUSEPORT 1 << 1 #define MM_BINDWITH_SO_REUSEADDR 1 << 2 odyssey-1.5.1-rc8/sources/include/machinarium/buf.h000066400000000000000000000030331517700303500222370ustar00rootroot00000000000000#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_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.5.1-rc8/sources/include/machinarium/build.h.cmake000066400000000000000000000006361517700303500236470ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/call.h000066400000000000000000000021231517700303500223750ustar00rootroot00000000000000#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; void *data; 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.5.1-rc8/sources/include/machinarium/channel.h000066400000000000000000000017061517700303500231000ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/channel_limit.h000066400000000000000000000002011517700303500242630ustar00rootroot00000000000000#pragma once typedef enum { MM_CHANNEL_UNLIMITED, MM_CHANNEL_LIMIT_HARD, MM_CHANNEL_LIMIT_SOFT, } mm_channel_limit_policy_t; odyssey-1.5.1-rc8/sources/include/machinarium/clock.h000066400000000000000000000022031517700303500225540ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #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; uint32_t time_sec; mm_buf_t timers; int timers_count; int timers_seq; }; void mm_clock_init(mm_clock_t *); void mm_clock_free(mm_clock_t *); void mm_clock_update(mm_clock_t *); int 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 (clock->timers_count == 0) { clock->active = 0; } } odyssey-1.5.1-rc8/sources/include/machinarium/compression.h000066400000000000000000000006751517700303500240350ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/cond.h000066400000000000000000000023221517700303500224060ustar00rootroot00000000000000#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); static inline void mm_cond_propagate(mm_cond_t *src, mm_cond_t *dst) { src->propagate = 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.5.1-rc8/sources/include/machinarium/context.h000066400000000000000000000025411517700303500231520ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #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 *); /* * 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.5.1-rc8/sources/include/machinarium/context_stack.h000066400000000000000000000006461517700303500243430ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/coroutine.h000066400000000000000000000023111517700303500234700ustar00rootroot00000000000000#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 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; void *call_ptr; mm_list_t joiners; mm_list_t link_join; mm_list_t link; int io_count; 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.5.1-rc8/sources/include/machinarium/coroutine_cache.h000066400000000000000000000012311517700303500246130ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/ds/000077500000000000000000000000001517700303500217215ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/machinarium/ds/hm.h000066400000000000000000000043261517700303500225030ustar00rootroot00000000000000#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 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_kvp_t *kvp, void **argv); typedef struct { /* mm_hashmap_kvp_t[] */ mm_list_t kvps; } mm_hashmap_bucket_t; typedef struct { mm_hashmap_kvp_t *kvp; mm_mutex_t *mu; int found; } mm_hashmap_keylock_t; typedef struct { 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; } mm_hashmap_t; 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 create); 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); odyssey-1.5.1-rc8/sources/include/machinarium/ds/queue.h000066400000000000000000000021061517700303500232150ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ /* * yet another concurrent queue implementation */ #include #include #include typedef void (*mm_queue_dtor_fn)(void *val); typedef struct { pthread_spinlock_t lock; size_t cap; size_t cap_bytes; size_t mask; size_t elsize; size_t size; size_t head; /* in bytes */ size_t tail; /* in bytes */ 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.5.1-rc8/sources/include/machinarium/ds/vector.h000066400000000000000000000014141517700303500233740ustar00rootroot00000000000000#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); odyssey-1.5.1-rc8/sources/include/machinarium/ds/vrb.h000066400000000000000000000033151517700303500226650ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #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; pthread_spinlock_t lock; } 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.5.1-rc8/sources/include/machinarium/epoll.h000066400000000000000000000002141517700303500225740ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include extern mm_pollif_t mm_epoll_if; odyssey-1.5.1-rc8/sources/include/machinarium/event.h000066400000000000000000000005651517700303500226130ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/event_mgr.h000066400000000000000000000013051517700303500234510ustar00rootroot00000000000000#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; 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); odyssey-1.5.1-rc8/sources/include/machinarium/fd.h000066400000000000000000000010121517700303500220470ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/idle.h000066400000000000000000000003371517700303500224040ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/io.h000066400000000000000000000055531517700303500221030ustar00rootroot00000000000000#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_tls_t *key; SSL_CTX *tls_ctx; mm_tls_ctx_t *next; }; 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; }; 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_shutdown_receptions(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); /* 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.5.1-rc8/sources/include/machinarium/iov.h000066400000000000000000000016441517700303500222660ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/list.h000066400000000000000000000022741517700303500224440ustar00rootroot00000000000000#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; } #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.5.1-rc8/sources/include/machinarium/loop.h000066400000000000000000000024201517700303500224330ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/lrand48.h000066400000000000000000000001511517700303500227350ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ void mm_lrand48_seed(void); odyssey-1.5.1-rc8/sources/include/machinarium/machinarium.h000066400000000000000000000246721517700303500237740ustar00rootroot00000000000000#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_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); /* 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 uint32_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 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 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 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 *); /* dns */ MACHINE_API int machine_getsockname(mm_io_t *, struct sockaddr *, int *); MACHINE_API int machine_getpeername(mm_io_t *, struct sockaddr *, int *); MACHINE_API int machine_getaddrinfo(char *addr, char *service, struct addrinfo *hints, struct addrinfo **res, uint32_t time_ms); /* 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.5.1-rc8/sources/include/machinarium/machine.h000066400000000000000000000024161517700303500230730ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include #include #include #include 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; mm_scheduler_t scheduler; mm_signalmgr_t signal_mgr; mm_eventmgr_t event_mgr; mm_msgcache_t msg_cache; mm_coroutine_cache_t coroutine_cache; mm_loop_t loop; mm_list_t link; 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_; } int machine_wait_nb(uint64_t machine_id); odyssey-1.5.1-rc8/sources/include/machinarium/machine_mgr.h000066400000000000000000000013201517700303500237310ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include typedef struct mm_machinemgr mm_machinemgr_t; struct mm_machinemgr { pthread_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.5.1-rc8/sources/include/machinarium/macro.h000066400000000000000000000014121517700303500225630ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/memory.h000066400000000000000000000014241517700303500227750ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/mm.h000066400000000000000000000007031517700303500220750ustar00rootroot00000000000000#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 pool_size; int coroutine_cache_size; int msg_cache_gc_size; }; struct mm { mm_config_t config; mm_machinemgr_t machine_mgr; mm_taskmgr_t task_mgr; }; extern mm_t machinarium; odyssey-1.5.1-rc8/sources/include/machinarium/msg.h000066400000000000000000000007171517700303500222570ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/msg_cache.h000066400000000000000000000016611517700303500234010ustar00rootroot00000000000000#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_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 wm) { cache->gc_watermark = 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.5.1-rc8/sources/include/machinarium/mutex.h000066400000000000000000000011661517700303500226320ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct { atomic_uint_fast64_t state; atomic_uintptr_t owner_machine; atomic_uint_fast64_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.5.1-rc8/sources/include/machinarium/poll.h000066400000000000000000000012221517700303500224270ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/ring_buffer.h000066400000000000000000000015671517700303500237650ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/scheduler.h000066400000000000000000000026021517700303500234420ustar00rootroot00000000000000#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 *, mm_coroutine_cache_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 *); void mm_scheduler_register_io(void); static inline void mm_scheduler_wakeup(mm_scheduler_t *scheduler, mm_coroutine_t *coroutine) { mm_scheduler_set(scheduler, coroutine, MM_CREADY); } odyssey-1.5.1-rc8/sources/include/machinarium/signal_mgr.h000066400000000000000000000013241517700303500236060ustar00rootroot00000000000000#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; 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.5.1-rc8/sources/include/machinarium/sleep_lock.h000066400000000000000000000012711517700303500236050ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/socket.h000066400000000000000000000023261517700303500227570ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include int mm_socket(int, int, int); int mm_socket_eventfd(unsigned 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.5.1-rc8/sources/include/machinarium/task.h000066400000000000000000000004241517700303500224260ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include typedef struct mm_task mm_task_t; typedef void (*mm_task_function_t)(void *); struct mm_task { mm_task_function_t function; void *arg; mm_event_t on_complete; }; odyssey-1.5.1-rc8/sources/include/machinarium/task_mgr.h000066400000000000000000000007731517700303500233020ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include #include typedef struct mm_taskmgr mm_taskmgr_t; struct mm_taskmgr { int workers_count; int *workers; mm_channel_t channel; mm_event_t event; }; void mm_taskmgr_init(mm_taskmgr_t *); int mm_taskmgr_start(mm_taskmgr_t *, int); void mm_taskmgr_stop(mm_taskmgr_t *); int mm_taskmgr_new(mm_taskmgr_t *, mm_task_function_t, void *, uint32_t); odyssey-1.5.1-rc8/sources/include/machinarium/thread.h000066400000000000000000000007471517700303500227430ustar00rootroot00000000000000#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 *, int, mm_thread_function_t, void *); int mm_thread_join(mm_thread_t *); int mm_thread_join_nb(mm_thread_t *thread); int mm_thread_set_name(char *); int mm_thread_disable_cancel(void); odyssey-1.5.1-rc8/sources/include/machinarium/timer.h000066400000000000000000000011461517700303500226060ustar00rootroot00000000000000#pragma once /* * machinarium. * * cooperative multitasking engine. */ #include #include typedef struct mm_timer mm_timer_t; typedef void (*mm_timer_callback_t)(mm_timer_t *); struct mm_timer { 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; } odyssey-1.5.1-rc8/sources/include/machinarium/tls.h000066400000000000000000000015201517700303500222640ustar00rootroot00000000000000#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); int mm_tls_get_cert_hash(mm_io_t *io, unsigned char (*cert_hash)[MM_CERT_HASH_LEN], unsigned int *len); odyssey-1.5.1-rc8/sources/include/machinarium/util.h000066400000000000000000000007461517700303500224500ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/wait_flag.h000066400000000000000000000007251517700303500234250ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/wait_group.h000066400000000000000000000011131517700303500236400ustar00rootroot00000000000000#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.5.1-rc8/sources/include/machinarium/wait_list.h000066400000000000000000000047471517700303500234770ustar00rootroot00000000000000#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 struct mm_sleepy { mm_event_t event; 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); odyssey-1.5.1-rc8/sources/include/machinarium/zpq_stream.h000066400000000000000000000021041517700303500236460ustar00rootroot00000000000000#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.5.1-rc8/sources/include/mb/000077500000000000000000000000001517700303500174145ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/mb/pg_wchar.h000066400000000000000000000714171517700303500213710ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/mdb_iamproxy.h000066400000000000000000000003401517700303500216560ustar00rootroot00000000000000#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.5.1-rc8/sources/include/misc.h000066400000000000000000000007601517700303500201250ustar00rootroot00000000000000#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.5.1-rc8/sources/include/module.h000066400000000000000000000044771517700303500204700ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #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); /* config */ typedef int (*config_rule_init_cb_t)(od_rule_t *rule, od_config_reader_t *cr, od_token_t *token); typedef int (*config_module_init_db_t)(od_config_reader_t *cr); /* 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; config_rule_init_cb_t config_rule_init_cb; config_module_init_db_t config_module_init_db; 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.5.1-rc8/sources/include/msg.h000066400000000000000000000003441517700303500177560ustar00rootroot00000000000000#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_t; odyssey-1.5.1-rc8/sources/include/multi_pool.h000066400000000000000000000047021517700303500213550ustar00rootroot00000000000000#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; pthread_spinlock_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) { pthread_spin_lock(&mpool->lock); } static inline void od_multi_pool_unlock(od_multi_pool_t *mpool) { pthread_spin_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.5.1-rc8/sources/include/murmurhash.h000066400000000000000000000001441517700303500213610ustar00rootroot00000000000000#pragma once typedef uint32_t od_hash_t; od_hash_t od_murmur_hash(const void *data, size_t size); odyssey-1.5.1-rc8/sources/include/od_c.h000066400000000000000000000037611517700303500201020ustar00rootroot00000000000000#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 #define od_likely(EXPR) __builtin_expect(!!(EXPR), 1) #define od_unlikely(EXPR) __builtin_expect(!!(EXPR), 0) #define od_read_mostly __attribute__((__section__(".data.read_mostly"))) #define od_container_of(N, T, F) ((T *)((char *)(N) - __builtin_offsetof(T, F))) #define OK_RESPONSE 0 #define NOT_OK_RESPONSE -1 #define _STRINGIFY(x) #x #define STRINGIFY(x) _STRINGIFY(x) #define CONCAT_(A, B) A##B #define CONCAT(A, B) CONCAT_(A, B) static const uint64_t interval_usec = 1000000ull; typedef int od_retcode_t; #define INVALID_COROUTINE_ID -1 /* only GCC supports the unused attribute */ #ifdef __GNUC__ #define od_attribute_unused() __attribute__((unused)) #else #define od_attribute_unused() #endif /* GCC support aligned, packed and noreturn */ #ifdef __GNUC__ #define od_attribute_aligned(a) __attribute__((aligned(a))) #define od_attribute_noreturn() __attribute__((noreturn)) #define od_attribute_packed() __attribute__((packed)) #endif #define FLEXIBLE_ARRAY_MEMBER /* empty */ #if defined __has_builtin #if __has_builtin(__builtin_unreachable) /* odyssey unreachable code */ #define od_unreachable() __builtin_unreachable() #endif #endif #ifndef od_unreachable #define od_unreachable() abort() #endif #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 odyssey-1.5.1-rc8/sources/include/od_dlsym.h000066400000000000000000000004451517700303500210040ustar00rootroot00000000000000#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.5.1-rc8/sources/include/od_error.h000066400000000000000000000025001517700303500207770ustar00rootroot00000000000000#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.5.1-rc8/sources/include/od_ldap.h000066400000000000000000000021611517700303500205710ustar00rootroot00000000000000#pragma once #ifdef LDAP_FOUND #include #include #include #include #include #include #include /* For functions ldap_unbind, ldap_search_s, ldap_simple_bind_s */ #define LDAP_DEPRECATED 1 #include typedef struct { od_id_t id; LDAP *conn; /* connect url */ od_ldap_endpoint_t *endpoint; /* link to actual settings; */ od_server_state_t state; od_global_t *global; void *route; int64_t idle_timestamp; od_list_t link; } od_ldap_server_t; extern od_retcode_t od_auth_ldap(od_client_t *cl, kiwi_password_t *tok); extern od_retcode_t od_ldap_server_free(od_ldap_server_t *serv); extern od_ldap_server_t *od_ldap_server_allocate(void); extern od_retcode_t od_ldap_server_init(od_logger_t *logger, od_ldap_server_t *serv, od_rule_t *rule); extern od_retcode_t od_ldap_server_prepare(od_logger_t *logger, od_ldap_server_t *serv, od_rule_t *rule, od_client_t *client); extern od_ldap_server_t *od_ldap_server_pull(od_logger_t *logger, od_rule_t *rule, bool auth_pool); #endif /* LDAP_FOUND */ odyssey-1.5.1-rc8/sources/include/od_memory.h000066400000000000000000000004501517700303500211600ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ 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); odyssey-1.5.1-rc8/sources/include/odyssey.h000066400000000000000000000003111517700303500206610ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. * * general definitions for all odyssey source files * should be included first */ #include #include odyssey-1.5.1-rc8/sources/include/option.h000066400000000000000000000052501517700303500205010ustar00rootroot00000000000000#pragma once #include #include #include #include extern void od_usage(od_instance_t *instance, char *path); extern int od_config_testing(od_instance_t *instance); 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_cli_options; static struct argp_option options[] = { { "verbose", OD_OPT_VERBOSE, 0, OPTION_ARG_OPTIONAL, "Log everything", 0 }, { "silent", OD_OPT_SILENT, 0, OPTION_ARG_OPTIONAL, "Do not log anything", 0 }, { "console", OD_OPT_CONSOLE, 0, OPTION_ARG_OPTIONAL, "Do not fork on startup", 0 }, { "log_to_stdout", OD_OPT_LOG_STDOUT, 0, OPTION_ARG_OPTIONAL, "Log to stdout", 0 }, { "test", OD_OPT_TEST, 0, OPTION_ARG_OPTIONAL, "Configuration testing", 0 }, { 0 } }; static inline error_t parse_opt(int key, char *arg, struct argp_state *state) { /* Get the input argument from argp_parse, which we know is a pointer to our arguments structure. */ od_arguments_t *arguments = state->input; od_instance_t *instance = arguments->instance; switch (key) { case 'q': case 's': case OD_OPT_SILENT: arguments->silent = 1; break; case 'v': case OD_OPT_VERBOSE: arguments->verbose = 1; break; case 'h': { od_usage(instance, instance->exec_path); } break; case OD_OPT_CONSOLE: { arguments->console = 1; } break; case OD_OPT_LOG_STDOUT: { arguments->log_stdout = 1; } break; case OD_OPT_TEST: { arguments->test = 1; } break; case ARGP_KEY_ARG: { if (state->arg_num >= 1) { /* Too many arguments. */ od_usage(instance, instance->exec_path); return ARGP_KEY_ERROR; } instance->config_file = od_strdup(arg); } break; case ARGP_KEY_END: if (state->arg_num < 1) { /* Not enough arguments. */ od_usage(instance, instance->exec_path); return ARGP_KEY_ERROR; } if (arguments->test == 1) { exit(od_config_testing(instance)); } break; default: return ARGP_ERR_UNKNOWN; } return 0; } 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); static inline void od_bind_args(struct argp *argp) { /* Program documentation. */ static char doc[] = "Odyssey - scalable postgresql connection pooler"; /* A description of the arguments we accept. */ static char args_doc[] = "/path/to/odyssey.conf"; memset(argp, 0, sizeof(struct argp)); argp->options = options; argp->parser = parse_opt; argp->args_doc = args_doc; argp->doc = doc; } odyssey-1.5.1-rc8/sources/include/pam.h000066400000000000000000000010301517700303500177360ustar00rootroot00000000000000#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.5.1-rc8/sources/include/parser.h000066400000000000000000000150061517700303500204650ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef struct od_token od_token_t; typedef struct od_keyword od_keyword_t; typedef struct od_parser od_parser_t; enum { OD_PARSER_EOF, OD_PARSER_ERROR, OD_PARSER_NUM, OD_PARSER_KEYWORD, OD_PARSER_SYMBOL, OD_PARSER_STRING }; struct od_token { int type; int line; union { int64_t num; struct { const char *pointer; int size; } string; } value; }; struct od_keyword { int id; const char *name; int name_len; }; #define od_keyword(name, token) \ { \ token, name, sizeof(name) - 1 \ } typedef struct { int accept_single_quotas; } od_parser_config_t; static inline void od_parser_config_init_default(od_parser_config_t *cfg) { memset(cfg, 0, sizeof(od_parser_config_t)); } struct od_parser { const char *pos; const char *end; od_token_t backlog[4]; int backlog_count; int line; od_parser_config_t config; }; static inline void od_parser_init(od_parser_t *parser, const char *string, int size) { parser->pos = string; parser->end = string + size; parser->line = 0; parser->backlog_count = 0; memset(parser->backlog, 0, sizeof(parser->backlog)); od_parser_config_init_default(&parser->config); } /* * parser initiated to parse queries instead of config * TODO: do not use this parser, use PG parsing */ static inline void od_parser_init_queries_mode(od_parser_t *parser, const char *string, int size) { od_parser_init(parser, string, size); parser->config.accept_single_quotas = 1; } static inline void od_parser_push(od_parser_t *parser, od_token_t *token) { assert(parser->backlog_count < 4); parser->backlog[parser->backlog_count] = *token; parser->backlog_count++; } static inline int od_parser_read_size_suffix_multiplier_end(od_parser_t *parser, int success_mult) { /* read first letter, like 'K' in 'Kb' */ parser->pos++; if (parser->pos >= parser->end) { /* * thats not a prefix * rollback pos to prev char and return no multiplier */ parser->pos--; return 1; } char next = *parser->pos; if (next != 'B' && next != 'b') { /* * thats not a prefix * rollback pos to prev char and return no multiplier */ parser->pos--; return 1; } /* read the B char */ parser->pos++; return success_mult; } static inline int od_parser_read_size_suffix_multiplier(od_parser_t *parser) { /* reads B/KB/MB/GB suffixes */ if (parser->pos >= parser->end) { /* no multiplier */ return 1; } char next = *parser->pos; switch (next) { case 'b': case 'B': /* skip 'B', return plain bytes number multiplier */ parser->pos++; return 1; case 'k': case 'K': return od_parser_read_size_suffix_multiplier_end(parser, 1024); case 'm': case 'M': return od_parser_read_size_suffix_multiplier_end(parser, 1024 * 1024); case 'g': case 'G': return od_parser_read_size_suffix_multiplier_end( parser, 1024 * 1024 * 1024); default: /* no multiplier suffix, do nothing */ return 1; } } static inline int od_parser_on_quota(const od_parser_t *parser) { char s = *parser->pos; return s == '"' || (parser->config.accept_single_quotas && s == '\''); } static inline int od_parser_next(od_parser_t *parser, od_token_t *token) { /* try to use backlog */ if (parser->backlog_count > 0) { *token = parser->backlog[parser->backlog_count - 1]; parser->backlog_count--; return token->type; } /* skip white spaces and comments */ for (;;) { while (parser->pos < parser->end && isspace(*parser->pos)) { if (*parser->pos == '\n') { parser->line++; } parser->pos++; } if (od_unlikely(parser->pos == parser->end)) { token->type = OD_PARSER_EOF; return token->type; } if (*parser->pos != '#') { break; } while (parser->pos < parser->end && *parser->pos != '\n') { parser->pos++; } if (parser->pos == parser->end) { token->type = OD_PARSER_EOF; return token->type; } parser->line++; } /* number */ int is_negative; is_negative = *parser->pos == '-' && (parser->pos + 1 < parser->end) && isdigit(parser->pos[1]); if (is_negative || isdigit(*parser->pos)) { token->type = OD_PARSER_NUM; token->line = parser->line; token->value.num = 0; if (is_negative) { parser->pos++; } while (parser->pos < parser->end && isdigit(*parser->pos)) { token->value.num = (token->value.num * 10) + *parser->pos - '0'; parser->pos++; } if (is_negative) { token->value.num *= -1; } int mult = od_parser_read_size_suffix_multiplier(parser); token->value.num *= mult; return token->type; } /* symbols */ if (!od_parser_on_quota(parser) && ispunct(*parser->pos)) { token->type = OD_PARSER_SYMBOL; token->line = parser->line; token->value.num = *parser->pos; parser->pos++; return token->type; } /* keyword */ if (isalpha(*parser->pos)) { token->type = OD_PARSER_KEYWORD; token->line = parser->line; token->value.string.pointer = parser->pos; while (parser->pos < parser->end && (*parser->pos == '_' || isalpha(*parser->pos) || isdigit(*parser->pos))) { parser->pos++; } token->value.string.size = parser->pos - token->value.string.pointer; return token->type; } /* string */ if (od_parser_on_quota(parser)) { char start_quota = *parser->pos; token->type = OD_PARSER_STRING; token->line = parser->line; parser->pos++; token->value.string.pointer = parser->pos; while (parser->pos < parser->end && *parser->pos != start_quota) { if (*parser->pos == '\n') { token->type = OD_PARSER_ERROR; return token->type; } if ((*parser->pos == '\\') && (parser->pos + 1 != parser->end)) { parser->pos += 2; } else { parser->pos++; } } if (od_unlikely(parser->pos == parser->end)) { token->type = OD_PARSER_ERROR; return token->type; } token->value.string.size = parser->pos - token->value.string.pointer; parser->pos++; return token->type; } /* error */ token->type = OD_PARSER_ERROR; token->line = parser->line; return token->type; } static inline od_keyword_t *od_keyword_match(od_keyword_t *list, od_token_t *token) { od_keyword_t *current = &list[0]; for (; current->name; current++) { if (current->name_len != token->value.string.size) { continue; } if (strncasecmp(current->name, token->value.string.pointer, token->value.string.size) == 0) { return current; } } return NULL; } static inline void od_token_to_string_dest(od_token_t *token, char *dest) { *dest = '\0'; strncat(dest, token->value.string.pointer, token->value.string.size); return; } odyssey-1.5.1-rc8/sources/include/pg_compat.h000066400000000000000000000074541517700303500211520ustar00rootroot00000000000000#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) assert(p) #define AssertMacro(p) ((void)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.5.1-rc8/sources/include/pid.h000066400000000000000000000010551517700303500177440ustar00rootroot00000000000000#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.5.1-rc8/sources/include/pool.h000066400000000000000000000030301517700303500201340ustar00rootroot00000000000000#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; uint64_t reset_timeout_ms; /* -------- makes sense only for transaction pooling --------------------------- */ int reserve_prepared_statement; /* ------------------------------------------------------------------------------ */ /* -------- 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.5.1-rc8/sources/include/port/000077500000000000000000000000001517700303500200025ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/port/pg_bswap.h000066400000000000000000000071231517700303500217600ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/port/simd.h000066400000000000000000000345231517700303500211160ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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.5.1-rc8/sources/include/postgres_fe.h000066400000000000000000000015641517700303500215150ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * 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 "pg_compat.h" #include "common/fe_memutils.h" /* IWYU pragma: end_exports */ #endif /* POSTGRES_FE_H */ odyssey-1.5.1-rc8/sources/include/prom_metrics.h000066400000000000000000000045011517700303500216720ustar00rootroot00000000000000#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.5.1-rc8/sources/include/pstmt.h000066400000000000000000000047101517700303500203400ustar00rootroot00000000000000#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 murmur("Q" + param bytes) * server name will be "pstmt_#" where # is global counter */ #include #include #define OD_PSTMT_NAME_PREFIX "odyssey_pstmt_" #define OD_PSTMT_NAME_MAX \ (sizeof(OD_PSTMT_NAME_PREFIX) + sizeof("18446744073709551615") + 1) typedef char od_pstmt_name_t[OD_PSTMT_NAME_MAX]; /* query and param bytes from Parse message */ typedef struct { void *data; size_t len; } od_pstmt_desc_t; struct od_pstmt { od_pstmt_desc_t desc; od_pstmt_name_t name; }; /* "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, const 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); /* "odyssey_pstmt_0" -> *od_pstmt_t */ mm_hashmap_t *od_server_pstmt_hashmap_create(void); void od_server_pstmt_hashmap_free(mm_hashmap_t *hm); int od_server_has_pstmt(od_server_t *server, const od_pstmt_t *pstmt); int od_server_add_pstmt(od_server_t *server, const 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); /* od_pstmt_desc_t -> od_pstmt_t */ mm_hashmap_t *od_global_pstmts_map_create(void); void od_global_pstmts_map_free(mm_hashmap_t *hm); void od_pstmt_next_name(od_pstmt_t *out); od_pstmt_t *od_pstmt_create_or_get(mm_hashmap_t *gm, od_pstmt_desc_t desc); /* 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); odyssey-1.5.1-rc8/sources/include/query.h000066400000000000000000000007671517700303500203460ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include /* execute query with (optional) single string param */ extern machine_msg_t *od_query_do(od_server_t *server, char *context, char *query, char *param); __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.5.1-rc8/sources/include/query_processing.h000066400000000000000000000017241517700303500225740ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ /* Minimalistic for now */ #include typedef enum { OD_QUERY_PROCESSING_LSET, OD_QUERY_PROCESSING_LTO, OD_QUERY_PROCESSING_LSHOW, OD_QUERY_PROCESSING_LODYSSEY, OD_QUERY_PROCESSING_LTSA, OD_QUERY_PROCESSING_LAPPNAME, OD_QUERY_PROCESSING_DEALLOCATE, OD_QUERY_PROCESSING_PREPARE, OD_QUERY_PROCESSING_ALL, } od_query_processing_keywords_t; extern od_keyword_t od_query_process_keywords[]; /* * Parse DEALLOCATE command: DEALLOCATE [ PREPARE ] { name | ALL } * Returns: * 1 if DEALLOCATE ALL detected * 0 if DEALLOCATE name detected (name and name_len are set) * -1 on parse error */ int od_parse_deallocate(const char *query, size_t query_len, const char **name, size_t *name_len); /* same as above, but for cases of parsing after reading DEALLOCATE on some parser */ int od_parse_deallocate_parser(od_parser_t *parser, const char **name, size_t *name_len); odyssey-1.5.1-rc8/sources/include/readahead.h000066400000000000000000000035341517700303500210720ustar00rootroot00000000000000#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) { assert(readahead->buf); return mm_virtual_rbuf_capacity(readahead->buf); } static inline int od_readahead_left(od_readahead_t *readahead) { 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); } odyssey-1.5.1-rc8/sources/include/relay.h000066400000000000000000000027161517700303500203110ustar00rootroot00000000000000#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); odyssey-1.5.1-rc8/sources/include/reset.h000066400000000000000000000002241517700303500203070ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include int od_reset(od_server_t *); odyssey-1.5.1-rc8/sources/include/restart_sync.h000066400000000000000000000007611517700303500217130ustar00rootroot00000000000000#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.5.1-rc8/sources/include/route.h000066400000000000000000000175641517700303500203420ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include 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; int last_heartbeat; pthread_mutex_t lock; od_error_logger_t *err_logger; bool extra_logging_enabled; od_list_t link; }; static inline int od_route_has_shared_pool(od_route_t *route) { 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) { 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)) { assert(route->shared_pool == NULL); return route->exclusive_pool; } 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; route->last_heartbeat = 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; } od_stat_init(&route->stats); od_stat_init(&route->stats_prev); kiwi_params_lock_init(&route->params); od_list_init(&route->link); pthread_mutex_init(&route->lock, NULL); return OK_RESPONSE; } static inline void od_route_free(od_route_t *route) { 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; } pthread_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) { pthread_mutex_lock(&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)); pthread_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, const od_address_t *address, 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.5.1-rc8/sources/include/route_id.h000066400000000000000000000031021517700303500207750ustar00rootroot00000000000000#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.5.1-rc8/sources/include/route_pool.h000066400000000000000000000143621517700303500213640ustar00rootroot00000000000000#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; pthread_mutex_t lock; od_list_t list; }; #define od_route_pool_lock(route_pool) pthread_mutex_lock(&route_pool.lock); #define od_route_pool_unlock(route_pool) pthread_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; pthread_mutex_init(&pool->lock, NULL); } static inline void od_route_pool_free(od_route_pool_t *pool) { if (pool == NULL) { return; } pthread_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; 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.5.1-rc8/sources/include/router.h000066400000000000000000000035661517700303500205210ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include struct od_router { pthread_mutex_t lock; od_rules_t rules; od_route_pool_t route_pool; /* clients */ od_atomic_u32_t clients; od_atomic_u32_t clients_routing; /* servers */ od_atomic_u32_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; }; #define od_router_lock(router) pthread_mutex_lock(&router->lock); #define od_router_unlock(router) pthread_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, const od_address_t *); /* 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.5.1-rc8/sources/include/router_cancel.h000066400000000000000000000011711517700303500220140ustar00rootroot00000000000000#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_free(cancel->storage); } } odyssey-1.5.1-rc8/sources/include/rules.h000066400000000000000000000147741517700303500203360ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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 { /* versioning */ int mark; int obsolete; int refs; 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; /* password */ char *password; int password_len; /* storage */ 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 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; uint64_t server_lifetime_us; 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_exit_flag; }; struct od_rules { pthread_mutex_t mu; od_list_t storages; #ifdef LDAP_FOUND od_list_t ldap_endpoints; #endif od_list_t rules; int next_order; machine_wait_flag_t *destroy_flag; }; /* 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); void od_rules_print(od_rules_t *, od_logger_t *); int od_rules_cleanup(od_rules_t *rules); 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 *, char *); od_rule_storage_t *od_rules_storage_add(od_rules_t *rules, od_rule_storage_t *storage); 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.5.1-rc8/sources/include/sasl.h000066400000000000000000000001661517700303500201340ustar00rootroot00000000000000#pragma once typedef enum { OD_SASL_ERROR_AUTH_IDENTITY = -4, OD_SASL_ERROR_MANDATORY_EXT = -5, } od_sasl_error_t; odyssey-1.5.1-rc8/sources/include/scram.h000066400000000000000000000042751517700303500203040ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include 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; uint8_t stored_key[32]; uint8_t server_key[32]; }; 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.5.1-rc8/sources/include/server.h000066400000000000000000000102031517700303500204710ustar00rootroot00000000000000#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; 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; int client_pinned; 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; 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->need_startup = 1; server->client_pinned = 0; 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); 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) { 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.5.1-rc8/sources/include/server_pool.h000066400000000000000000000144461517700303500215370ustar00rootroot00000000000000#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) #ifdef LDAP_FOUND OD_SERVER_POOL_FREE_DECLARE(ldap, od_ldap_server_t, od_ldap_server_free) #endif #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) #ifdef LDAP_FOUND OD_SERVER_POOL_SET_DECLARE(ldap, od_ldap_server_t) #endif #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: \ 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) #ifdef LDAP_FOUND OD_SERVER_POOL_NEXT_DECLARE(ldap, od_ldap_server_t) #endif 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: 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.5.1-rc8/sources/include/setproctitle.h000066400000000000000000000003001517700303500217010ustar00rootroot00000000000000#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.5.1-rc8/sources/include/shared_pool.h000066400000000000000000000007451517700303500214740ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include struct od_shared_pool { od_multi_pool_t *mpool; pthread_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.5.1-rc8/sources/include/sighandler.h000066400000000000000000000003731517700303500213120ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include void od_system_signal_handler(void *arg); void od_system_shutdown(od_system_t *system, od_instance_t *instance); odyssey-1.5.1-rc8/sources/include/soft_oom.h000066400000000000000000000011561517700303500210170ustar00rootroot00000000000000#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.5.1-rc8/sources/include/stat.h000066400000000000000000000156521517700303500201530ustar00rootroot00000000000000#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; od_atomic_u64_t count_query; od_atomic_u64_t count_tx; od_atomic_u64_t query_time; od_atomic_u64_t tx_time; od_atomic_u64_t count_wait; od_atomic_u64_t wait_time_us; od_atomic_u64_t recv_server; od_atomic_u64_t recv_client; od_atomic_u64_t count_parse; od_atomic_u64_t count_parse_reuse; 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) { od_atomic_u64_add(&stat->wait_time_us, wait_time_us); od_atomic_u64_inc(&stat->count_wait); } static inline void od_stat_parse(od_stat_t *stat) { od_atomic_u64_inc(&stat->count_parse); } static inline void od_stat_parse_reuse(od_stat_t *stat) { od_atomic_u64_inc(&stat->count_parse_reuse); } 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; od_atomic_u64_add(&stat->query_time, diff); od_atomic_u64_inc(&stat->count_query); 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) { od_atomic_u64_add(&stat->tx_time, diff); od_atomic_u64_inc(&stat->count_tx); 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) { od_atomic_u64_add(&stat->recv_server, bytes); } static inline void od_stat_recv_client(od_stat_t *stat, uint64_t bytes) { od_atomic_u64_add(&stat->recv_client, bytes); } static inline void od_stat_copy(od_stat_t *dst, od_stat_t *src) { dst->count_query = od_atomic_u64_of(&src->count_query); dst->count_tx = od_atomic_u64_of(&src->count_tx); dst->query_time = od_atomic_u64_of(&src->query_time); dst->tx_time = od_atomic_u64_of(&src->tx_time); dst->count_wait = od_atomic_u64_of(&src->count_wait); dst->wait_time_us = od_atomic_u64_of(&src->wait_time_us); dst->recv_client = od_atomic_u64_of(&src->recv_client); dst->recv_server = od_atomic_u64_of(&src->recv_server); dst->count_parse = od_atomic_u64_of(&src->count_parse); dst->count_parse_reuse = od_atomic_u64_of(&src->count_parse_reuse); } static inline void od_stat_sum(od_stat_t *sum, od_stat_t *stat) { sum->count_query += od_atomic_u64_of(&stat->count_query); sum->count_tx += od_atomic_u64_of(&stat->count_tx); sum->query_time += od_atomic_u64_of(&stat->query_time); sum->tx_time += od_atomic_u64_of(&stat->tx_time); sum->count_wait += od_atomic_u64_of(&stat->count_wait); sum->wait_time_us += od_atomic_u64_of(&stat->wait_time_us); sum->recv_client += od_atomic_u64_of(&stat->recv_client); sum->recv_server += od_atomic_u64_of(&stat->recv_server); sum->count_parse += od_atomic_u64_of(&stat->count_parse); sum->count_parse_reuse += od_atomic_u64_of(&stat->count_parse_reuse); } static inline void od_stat_update_of(od_atomic_u64_t *prev, od_atomic_u64_t *current) { /* todo: this could be made more optimal */ /* prev <= current */ __atomic_store((uint64_t *)prev, (uint64_t *)current, __ATOMIC_SEQ_CST); } 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); } 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 = od_atomic_u64_of(¤t->count_query) - od_atomic_u64_of(&prev->count_query); uint64_t count_tx; count_tx = od_atomic_u64_of(¤t->count_tx) - od_atomic_u64_of(&prev->count_tx); uint64_t count_wait; count_wait = od_atomic_u64_of(¤t->count_wait) - od_atomic_u64_of(&prev->count_wait); uint64_t count_parse; count_parse = od_atomic_u64_of(¤t->count_parse) - od_atomic_u64_of(&prev->count_parse); uint64_t count_parse_reuse; count_parse_reuse = od_atomic_u64_of(¤t->count_parse_reuse) - od_atomic_u64_of(&prev->count_parse_reuse); 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 = (od_atomic_u64_of(¤t->query_time) - od_atomic_u64_of(&prev->query_time)) / count_query; } if (count_tx > 0) { avg->tx_time = (od_atomic_u64_of(¤t->tx_time) - od_atomic_u64_of(&prev->tx_time)) / count_tx; } if (count_wait > 0) { avg->wait_time_us = (od_atomic_u64_of(¤t->wait_time_us) - od_atomic_u64_of(&prev->wait_time_us)) / count_wait; } avg->recv_client = ((od_atomic_u64_of(¤t->recv_client) - od_atomic_u64_of(&prev->recv_client)) * interval_usec) / interval_us; avg->recv_server = ((od_atomic_u64_of(¤t->recv_server) - od_atomic_u64_of(&prev->recv_server)) * interval_usec) / interval_us; } odyssey-1.5.1-rc8/sources/include/status.h000066400000000000000000000121421517700303500205120ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef enum { OD_UNDEF, OD_OK, OD_SKIP, OD_REPLACED, OD_REQ_SYNC, OD_ATTACH, OD_DETACH, 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_ECLIENT_READ, OD_ECLIENT_PROTOCOL_ERROR, OD_ECLIENT_COPY_IN_XPROTO, OD_ECLIENT_WRITE, 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_REQ_SYNC: return "OD_REQ_SYNC"; case OD_ATTACH: return "OD_ATTACH"; case OD_DETACH: return "OD_DETACH"; 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_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_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_ECLIENT_WRITE, 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.5.1-rc8/sources/include/storage.h000066400000000000000000000056051517700303500206410ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include typedef struct od_rule_storage od_rule_storage_t; typedef struct od_storage_watchdog od_storage_watchdog_t; /* 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; /* soft shutdown on reload */ pthread_mutex_t mu; int online; od_global_t *global; od_rule_storage_t *storage; machine_wait_flag_t *is_finished; }; od_storage_watchdog_t *od_storage_watchdog_allocate(od_global_t *); int od_storage_watchdog_free(od_storage_watchdog_t *watchdog); /* */ typedef struct od_storage_endpoint od_storage_endpoint_t; typedef struct { uint64_t last_update_time_ms; pthread_spinlock_t values_lock; int alive; bool is_read_write; } 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; }; int od_storage_parse_endpoints(const char *host_str, od_storage_endpoint_t **out, size_t *count); typedef struct od_auth_cache_value od_auth_cache_value_t; struct od_auth_cache_value { uint64_t timestamp; char *passwd; uint32_t passwd_len; }; struct od_rule_storage { od_tls_opts_t *tls_opts; char *name; char *type; od_rule_storage_type_t storage_type; /* round-robin atomic counter for endpoint selection */ atomic_size_t rr_counter; 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; od_hashmap_t *acache; od_list_t link; int endpoints_status_poll_interval_ms; }; /* storage API */ od_rule_storage_t *od_rules_storage_allocate(void); od_rule_storage_t *od_rules_storage_copy(od_rule_storage_t *); od_storage_endpoint_t *od_rules_storage_next_endpoint(od_rule_storage_t *); od_storage_endpoint_t * od_rules_storage_localhost_or_next_endpoint(od_rule_storage_t *); void od_rules_storage_free(od_rule_storage_t *); /* watchdog */ void od_storage_watchdog_watch(void *arg); odyssey-1.5.1-rc8/sources/include/stream.h000066400000000000000000000020731517700303500204640ustar00rootroot00000000000000#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.5.1-rc8/sources/include/system.h000066400000000000000000000014651517700303500205210ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include struct od_system_server { mm_io_t *io; machine_tls_t *tls; od_config_listen_t *config; struct addrinfo *addr; od_global_t *global; od_list_t link; od_id_t sid; atomic_bool closed; volatile bool pre_exited; int64_t coro_id; }; void od_system_server_free(od_system_server_t *server); od_system_server_t *od_system_server_init(void); struct od_system { int64_t machine; int64_t sighandler_machine; 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); odyssey-1.5.1-rc8/sources/include/sysv.h000066400000000000000000000003171517700303500201740ustar00rootroot00000000000000#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.5.1-rc8/sources/include/tdigest.h000066400000000000000000000044051517700303500206350ustar00rootroot00000000000000#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.5.1-rc8/sources/include/tests/000077500000000000000000000000001517700303500201605ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/tests/odyssey_test.h000066400000000000000000000063141517700303500230730ustar00rootroot00000000000000#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.5.1-rc8/sources/include/thread_global.h000066400000000000000000000007231517700303500217600ustar00rootroot00000000000000#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 od_retcode_t od_thread_global_free(od_thread_global *gl); odyssey-1.5.1-rc8/sources/include/tls.h000066400000000000000000000006451517700303500177760ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include machine_tls_t *od_tls_frontend(od_config_listen_t *); 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.5.1-rc8/sources/include/tls_config.h000066400000000000000000000015541517700303500213230ustar00rootroot00000000000000 #pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ typedef enum { OD_CONFIG_TLS_DISABLE, OD_CONFIG_TLS_ALLOW, 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_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 *); odyssey-1.5.1-rc8/sources/include/tsa.h000066400000000000000000000021361517700303500177600ustar00rootroot00000000000000#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_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_UNDEF: return "no specified"; } return ""; } static inline char * od_target_session_attrs_to_pg_mode_str(od_target_session_attrs_t tsa) { switch (tsa) { case OD_TARGET_SESSION_ATTRS_RW: return "primary"; case OD_TARGET_SESSION_ATTRS_RO: return "standby"; case OD_TARGET_SESSION_ATTRS_ANY: return "any"; 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.5.1-rc8/sources/include/types.h000066400000000000000000000032671517700303500203430ustar00rootroot00000000000000#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_reader od_config_reader_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; odyssey-1.5.1-rc8/sources/include/util.h000066400000000000000000000046741517700303500201570ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include 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, char *fmt, va_list args) { vasprintf(bufp, fmt, args); if (*bufp == NULL) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline int od_asprintf(char **__restrict bufp, char *fmt, ...) { va_list args; va_start(args, fmt); int rc = vasprintf(bufp, fmt, args); va_end(args); if (rc == -1) { return NOT_OK_RESPONSE; } if (*bufp == 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", left_len, left, right); } static inline char *od_strdup_from_buf(const char *source, size_t size) { char *str = od_malloc(size + 1); 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); } odyssey-1.5.1-rc8/sources/include/utils/000077500000000000000000000000001517700303500201565ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/include/utils/ascii.h000066400000000000000000000043271517700303500214250ustar00rootroot00000000000000/*----------------------------------------------------------------------- * 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.5.1-rc8/sources/include/worker.h000066400000000000000000000006731517700303500205060ustar00rootroot00000000000000#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 *); odyssey-1.5.1-rc8/sources/include/worker_pool.h000066400000000000000000000041151517700303500215320ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include struct od_worker_pool { od_worker_t *pool; od_atomic_u32_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_wait(void) { machine_sleep(1); } 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) { /* * In fact we cannot wait anything here - machines may be in epoll * waiting * No new TLS handshakes should be initiated, so, just wait a bit. * machine_sleep(1); */ for (uint32_t i = 0; i < pool->count; i++) { od_worker_t *worker = &pool->pool[i]; int rc = machine_wait(worker->machine); if (rc != MM_OK_RETCODE) { return; } 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) { uint32_t next; uint32_t oldValue; for (;;) { oldValue = od_atomic_u32_of(&pool->round_robin); next = oldValue + 1 == pool->count ? 0 : oldValue + 1; if (od_atomic_u32_cas(&pool->round_robin, oldValue, next) == oldValue) { break; } } od_worker_t *worker; worker = &pool->pool[next]; machine_channel_write(worker->task_channel, msg); } odyssey-1.5.1-rc8/sources/include/xplan.h000066400000000000000000000056551517700303500203240ustar00rootroot00000000000000#pragma once /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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 } od_xplan_delta_type_t; typedef struct { od_xplan_delta_type_t type; const char *client_pstmt; const od_pstmt_t *pstmt; } 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, } 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 * * can be NULL, owned by xplan */ machine_msg_t *vresp; } 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.5.1-rc8/sources/instance.c000066400000000000000000000334451517700303500173540ustar00rootroot00000000000000 /* * 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 static inline 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); 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, "odyssey %s", 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_config_reader_import(&instance->config, &router.rules, &error, &extensions, &global, &hba.rules, instance->config_file); if (rc == -1) { od_error(&instance->logger, "config", NULL, NULL, "%s", error.error); 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 void od_bind_version(void) { char features[128]; fill_supported_features_string(features, sizeof(features)); #ifdef ODYSSEY_VERSION_GIT od_asprintf((char **__restrict)&argp_program_version, "odyssey %s (git %s) %s%s\ncompiled by %s", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT, ODYSSEY_BUILD_TYPE, features, ODYSSEY_COMPILER_STRING); #else od_asprintf((char **__restrict)&argp_program_version, "odyssey %s %s%s\ncompiled by %s", ODYSSEY_VERSION_NUMBER, ODYSSEY_BUILD_TYPE, features, ODYSSEY_COMPILER_STRING); #endif } 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)); struct argp argp; od_bind_args(&argp); od_bind_version(); /* 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 */ int argindx; /* index of first unparsed indx */ if (argp_parse(&argp, argc, argv, 0, &argindx, &args) != OK_RESPONSE) { return NOT_OK_RESPONSE; } 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 */ od_error_t error; od_error_init(&error); int rc; rc = od_config_reader_import(&instance->config, &router.rules, &error, extensions, global, &hba.rules, instance->config_file); if (rc == -1) { od_error(&instance->logger, "config", NULL, NULL, "%s", error.error); 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, "odyssey %s (git: %s) %s%s", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT, ODYSSEY_BUILD_TYPE, features); #else od_log(&instance->logger, "init", NULL, NULL, "odyssey %s %s%s", ODYSSEY_VERSION_NUMBER, ODYSSEY_BUILD_TYPE, features); #endif od_log(&instance->logger, "init", NULL, NULL, ""); /* print configuration */ od_log(&instance->logger, "init", NULL, NULL, "using configuration file '%s'", instance->config_file); od_log(&instance->logger, "init", NULL, NULL, ""); if (instance->config.log_config) { od_config_print(&instance->config, &instance->logger); od_rules_print(&router.rules, &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_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); 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: 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); } mm_hashmap_t *od_instance_get_pstmts_map(od_instance_t *instance) { return instance->pstmts; } odyssey-1.5.1-rc8/sources/io.c000066400000000000000000000070261517700303500161530ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include int od_io_write_raw(od_io_t *io, const void *buf, size_t size, size_t *processed, uint32_t timeout_ms) { *processed = 0; size_t total = 0; const char *pos = buf; 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; continue; } 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; } 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) { 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; continue; } 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; } 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) { mm_errno_set(EINPROGRESS); 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) }; assert(sizeof(flush) == 5); size_t unused; return od_io_write_raw(io, flush, sizeof(flush), &unused, timeout_ms); } odyssey-1.5.1-rc8/sources/kiwi/000077500000000000000000000000001517700303500163365ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/kiwi/README.md000066400000000000000000000052441517700303500176220ustar00rootroot00000000000000**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.5.1-rc8/sources/kiwi/md5.c000066400000000000000000000160571517700303500172000ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/kiwi/options.c000066400000000000000000000063311517700303500202000ustar00rootroot00000000000000 /* * 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(struct iovec)); 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 size_t unescape(const char *str, size_t len, char *dst) { int escape = 0; size_t rlen = 0; for (size_t i = 0; i < len; ++i) { if (!escape && str[i] == '\\') { escape = 1; } else { dst[rlen++] = str[i]; escape = 0; } } dst[rlen] = 0; return rlen; } 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); nlen = unescape(str, nlen, name); vlen = unescape(str + equal_pos + 1, vlen, val); kiwi_long_option_rewrite(name, (int)nlen); return kiwi_vars_update(vars, name, (int)nlen + 1, val, (int)vlen + 1); } 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.5.1-rc8/sources/kiwi/var.c000066400000000000000000000064701517700303500173010ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/ldap.c000066400000000000000000000454751517700303500164760ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include od_retcode_t od_ldap_server_free(od_ldap_server_t *serv) { od_list_unlink(&serv->link); /* free memory alloc from LDAP lib */ if (serv->conn) { ldap_unbind(serv->conn); } od_free(serv); return OK_RESPONSE; } static inline 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; } static inline int od_init_ldap_conn(LDAP **l, char *uri) { od_retcode_t rc = ldap_initialize(l, uri); if (rc != LDAP_SUCCESS) { /* * set to null, we do not need to unbind this * ldap_initialize frees assosated memory */ *l = NULL; return NOT_OK_RESPONSE; } int ldapversion = LDAP_VERSION3; rc = ldap_set_option(*l, LDAP_OPT_PROTOCOL_VERSION, &ldapversion); if (rc != LDAP_SUCCESS) { /* same as above */ *l = NULL; return NOT_OK_RESPONSE; } return 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:%d", 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; } od_retcode_t od_ldap_search_storage_credentials(od_logger_t *logger, struct berval **values, od_rule_t *rule, od_client_t *client) { int i; for (i = 0; i < ldap_count_values_len(values); i++) { char host_db[128]; od_snprintf(host_db, sizeof(host_db), "%s_%s", rule->storage->host, client->startup.database.value); 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[128]; 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 OK_RESPONSE; } } } } od_debug(logger, "auth_ldap", client, NULL, "error: route does not match any user attribute"); return NOT_OK_RESPONSE; } od_retcode_t od_ldap_server_prepare(od_logger_t *logger, od_ldap_server_t *serv, od_rule_t *rule, od_client_t *client) { od_retcode_t rc; char *auth_user = NULL; if (serv->endpoint->ldapbasedn) { /* copy pasted from ./src/backend/libpq/auth.c:2635 */ 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 (serv->endpoint->ldapsearchattribute) { od_asprintf(&filter, "(%s=%s)", serv->endpoint->ldapsearchattribute, client->startup.user.value); } else { od_asprintf(&filter, "(uid=%s)", client->startup.user.value); } if (serv->endpoint->ldapsearchfilter) { char *prev_filter = od_strdup(filter); od_free(filter); if (prev_filter == NULL) { return NOT_OK_RESPONSE; } od_asprintf(&filter, "(&%s%s)", prev_filter, serv->endpoint->ldapsearchfilter); od_free(prev_filter); } rc = ldap_search_s(serv->conn, serv->endpoint->ldapbasedn, LDAP_SCOPE_SUBTREE, filter, attributes, 0, &search_message); od_debug(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(logger, "auth_ldap", client, NULL, "basednn search result: %d", rc); return NOT_OK_RESPONSE; } count = ldap_count_entries(serv->conn, search_message); if (count != 1) { od_error(logger, "auth_ldap", client, NULL, "basedn search entries count: %d", count); if (count == 0) { ldap_msgfree(search_message); return LDAP_INSUFFICIENT_ACCESS; } ldap_msgfree(search_message); return NOT_OK_RESPONSE; } entry = ldap_first_entry(serv->conn, search_message); dn = ldap_get_dn(serv->conn, entry); if (dn == NULL) { /* TODO: report err */ return NOT_OK_RESPONSE; } if (rule->ldap_storage_credentials_attr) { struct berval **values = NULL; values = ldap_get_values_len(serv->conn, entry, attributes[0]); if (ldap_count_values_len(values) > 0) { rc = od_ldap_search_storage_credentials( logger, values, rule, client); if (rc != OK_RESPONSE) { ldap_memfree(dn); ldap_value_free_len(values); ldap_msgfree(search_message); return LDAP_INSUFFICIENT_ACCESS; } } else { od_debug(logger, "auth_ldap", client, NULL, "error: empty search results"); ldap_memfree(dn); ldap_value_free_len(values); ldap_msgfree(search_message); return LDAP_INSUFFICIENT_ACCESS; } ldap_value_free_len(values); } auth_user = od_strdup(dn); ldap_memfree(dn); ldap_msgfree(search_message); if (auth_user == NULL) { return NOT_OK_RESPONSE; } } else { od_asprintf(&auth_user, "%s%s%s", serv->endpoint->ldapprefix ? serv->endpoint->ldapprefix : "", client->startup.user.value, serv->endpoint->ldapsuffix ? serv->endpoint->ldapsuffix : ""); } client->ldap_auth_dn = auth_user; return OK_RESPONSE; } od_ldap_server_t *od_ldap_server_allocate(void) { od_ldap_server_t *serv = od_malloc(sizeof(od_ldap_server_t)); serv->conn = NULL; serv->endpoint = NULL; return serv; } od_retcode_t od_ldap_server_init(od_logger_t *logger, od_ldap_server_t *server, od_rule_t *rule) { od_retcode_t rc; od_id_generate(&server->id, "ls"); od_list_init(&server->link); server->global = NULL; od_ldap_endpoint_t *le = rule->ldap_endpoint; server->endpoint = le; if (od_init_ldap_conn(&server->conn, le->ldapurl) != OK_RESPONSE) { return NOT_OK_RESPONSE; } rc = ldap_simple_bind_s(server->conn, server->endpoint->ldapbinddn ? server->endpoint->ldapbinddn : "", server->endpoint->ldapbindpasswd ? server->endpoint->ldapbindpasswd : ""); if (rc) { od_error(logger, "auth_ldap", NULL, NULL, "basednn simple bind result: %d", rc); } return rc; } static inline int od_ldap_server_auth(od_ldap_server_t *serv, od_client_t *cl, kiwi_password_t *tok) { int rc; rc = ldap_simple_bind_s(serv->conn, cl->ldap_auth_dn, tok->password); od_route_t *route = cl->route; if (route->rule->client_fwd_error) { od_ldap_error_report_client(cl, rc); } return rc; } /*#define USE_POOL */ od_ldap_server_t *od_ldap_server_pull(od_logger_t *logger, od_rule_t *rule, bool auth_pool) { (void)auth_pool; od_ldap_endpoint_t *le = rule->ldap_endpoint; od_ldap_server_t *ldap_server = NULL; #if USE_POOL od_server_pool_t *ldap_server_pool; if (auth_pool) { ldap_server_pool = le->ldap_auth_pool; } else { ldap_server_pool = le->ldap_search_pool; } od_debug(logger, "auth_ldap", NULL, NULL, "total connections in selected pool: %d", od_server_pool_total(ldap_server_pool)); #endif od_ldap_endpoint_lock(le); #if USE_POOL /* get client server from route server pool */ for (;;) { ldap_server = od_ldap_server_pool_next(ldap_server_pool, OD_SERVER_IDLE); if (ldap_server) { od_debug(logger, "auth_ldap", NULL, NULL, "pulling ldap_server from ldap_pool"); if (rule->ldap_pool_ttl > 0) { if (time(NULL) - ldap_server->idle_timestamp > rule->ldap_pool_ttl) { od_debug( logger, "auth_ldap", NULL, NULL, "bad ldap_server_ttl - closing ldap connection"); od_ldap_server_pool_set( ldap_server_pool, ldap_server, OD_SERVER_UNDEF); od_ldap_server_free(ldap_server); ldap_server = NULL; od_ldap_endpoint_unlock(le); break; } } od_ldap_server_pool_set(ldap_server_pool, ldap_server, OD_SERVER_ACTIVE); od_ldap_endpoint_unlock(le); break; } if (false) { /* special case, when we are interested only in an idle connection * and do not want to start a new one */ /* NOT IMPL */ od_ldap_endpoint_unlock(le); return NULL; } else { /* Maybe start new connection, if pool_size is zero */ /* Maybe start new connection, if we still have capacity for it */ int connections_in_pool = od_server_pool_total(ldap_server_pool); int pool_size = rule->ldap_pool_size; if (pool_size == 0 || connections_in_pool < pool_size) { /* * TODO: better limit logic here * We are allowed to spun new server connection */ od_debug( logger, "auth_ldap", NULL, NULL, "spun new connection to ldap server %s", rule->ldap_endpoint_name); break; } } /* * Wait wakeup condition for pool_timeout milliseconds. * * The condition triggered when a server connection * put into idle state by DETACH events. */ od_ldap_endpoint_unlock(le); uint32_t timeout = rule->ldap_pool_timeout; if (timeout == 0) { timeout = UINT32_MAX; } rc = od_ldap_endpoint_wait(le, timeout); if (rc == -1) { return NULL; } od_ldap_endpoint_lock(le); } #endif if (ldap_server == NULL) { /* create new server object */ ldap_server = od_ldap_server_allocate(); int ldap_rc = od_ldap_server_init(logger, ldap_server, rule); if (ldap_rc != LDAP_SUCCESS) { od_error(logger, "auth_ldap", NULL, NULL, "ldap server initialsize failed (rc=%d)", ldap_rc); od_ldap_server_free(ldap_server); od_ldap_endpoint_unlock(le); return NULL; } #if USE_POOL od_ldap_server_pool_set(ldap_server_pool, ldap_server, OD_SERVER_ACTIVE); #endif od_ldap_endpoint_unlock(le); } return ldap_server; } static inline od_retcode_t od_ldap_server_attach(od_client_t *client) { od_instance_t *instance = client->global->instance; od_logger_t *logger = &instance->logger; od_retcode_t rc; /* get client server from route server pool */ od_ldap_server_t *server = od_ldap_server_pull(logger, client->rule, false); if (server == NULL) { od_error(&instance->logger, "auth_ldap_prepare", client, NULL, "failed to get ldap connection on attach"); if (client->rule->client_fwd_error) { od_ldap_error_report_client(client, NOT_OK_RESPONSE); } return NOT_OK_RESPONSE; } od_ldap_endpoint_lock(client->rule->ldap_endpoint); rc = od_ldap_server_prepare(logger, server, client->rule, client); if (rc == NOT_OK_RESPONSE) { od_debug(&instance->logger, "auth_ldap", client, NULL, "closing bad ldap connection, need relogin"); #if USE_POOL od_ldap_server_pool_set( client->rule->ldap_endpoint->ldap_search_pool, server, OD_SERVER_UNDEF); #endif od_ldap_server_free(server); } else { server->idle_timestamp = (int)time(NULL); #if USE_POOL od_ldap_server_pool_set( client->rule->ldap_endpoint->ldap_search_pool, server, OD_SERVER_IDLE); #else od_ldap_server_free(server); #endif } od_ldap_endpoint_unlock(client->rule->ldap_endpoint); if (rc != OK_RESPONSE) { od_error(&instance->logger, "auth_ldap", client, NULL, "failed to get ldap connection on attach"); if (client->rule->client_fwd_error) { od_ldap_error_report_client(client, rc); } return NOT_OK_RESPONSE; } return OK_RESPONSE; } od_retcode_t od_auth_ldap(od_client_t *cl, kiwi_password_t *tok) { od_instance_t *instance = cl->global->instance; od_retcode_t rc; int ldap_rc; if (cl->rule->ldap_storage_credentials_attr && cl->rule->ldap_endpoint_name) { rc = OK_RESPONSE; } else { rc = od_ldap_server_attach(cl); } if (rc != OK_RESPONSE) { return rc; } od_ldap_server_t *serv = od_ldap_server_pull(&instance->logger, cl->rule, true); if (serv == NULL) { od_error(&instance->logger, "auth_ldap", cl, NULL, "failed to get ldap connection for auth"); return NOT_OK_RESPONSE; } ldap_rc = od_ldap_server_auth(serv, cl, tok); od_ldap_endpoint_lock(cl->rule->ldap_endpoint); switch (ldap_rc) { case LDAP_SUCCESS: { serv->idle_timestamp = (int)time(NULL); #if USE_POOL od_ldap_server_pool_set(cl->rule->ldap_endpoint->ldap_auth_pool, serv, OD_SERVER_IDLE); #else od_ldap_server_free(serv); #endif rc = OK_RESPONSE; break; } case LDAP_INVALID_SYNTAX: /* fallthrough */ case LDAP_INVALID_CREDENTIALS: { serv->idle_timestamp = (int)time(NULL); #if USE_POOL od_ldap_server_pool_set(cl->rule->ldap_endpoint->ldap_auth_pool, serv, OD_SERVER_IDLE); #else od_ldap_server_free(serv); #endif rc = NOT_OK_RESPONSE; break; } default: { /*Need to rebind */ #if USE_POOL od_ldap_server_pool_set(cl->rule->ldap_endpoint->ldap_auth_pool, serv, OD_SERVER_UNDEF); #endif od_ldap_server_free(serv); rc = NOT_OK_RESPONSE; break; } } od_ldap_endpoint_unlock(cl->rule->ldap_endpoint); return rc; } od_retcode_t od_ldap_conn_close(od_attribute_unused() od_route_t *route, od_ldap_server_t *server) { ldap_unbind(server->conn); od_list_unlink(&server->link); return OK_RESPONSE; } /*---------------------------------------------------------------------------------------- */ /* 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; #ifdef USE_POOL od_server_pool_t *ldap_auth_pool = od_malloc(sizeof(od_server_pool_t)); od_server_pool_init(ldap_auth_pool); le->ldap_auth_pool = ldap_auth_pool; od_server_pool_t *ldap_search_pool = od_malloc(sizeof(od_server_pool_t)); od_server_pool_init(ldap_search_pool); le->ldap_search_pool = ldap_search_pool; #endif le->wait_bus = machine_channel_create(); if (le->wait_bus == NULL) { od_ldap_endpoint_free(le); return NULL; } atomic_store(&le->refs, 1); pthread_mutex_init(&le->lock, NULL); 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); #if USE_POOL if (le->ldap_search_pool) { od_ldap_server_pool_free(le->ldap_search_pool); } if (le->ldap_auth_pool) { od_ldap_server_pool_free(le->ldap_auth_pool); } #endif pthread_mutex_destroy(&le->lock); if (le->wait_bus) { machine_channel_free(le->wait_bus); } 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; } odyssey-1.5.1-rc8/sources/logger.c000066400000000000000000000525001517700303500170200ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #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" }; 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->fd = -1; atomic_init(&logger->loaded, 0); memset(&logger->tasks, 0, sizeof(mm_queue_t)); logger->slots = NULL; atomic_init(&logger->dropped_lines, 0); od_list_init(&logger->free_slots); pthread_spin_init(&logger->free_slots_lock, PTHREAD_PROCESS_PRIVATE); mm_wait_list_init(&logger->notifier, NULL); /* 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); od_retcode_t od_logger_load(od_logger_t *logger) { if (!logger->async) { return OK_RESPONSE; } if (atomic_load(&logger->loaded) == 1) { return NOT_OK_RESPONSE; } int rc = mm_queue_init(&logger->tasks, (size_t)logger->queue_depth, sizeof(od_logger_slot_t *), NULL); if (rc != 0) { return NOT_OK_RESPONSE; } logger->slots = od_malloc(logger->queue_depth * sizeof(od_logger_slot_t)); if (logger->slots == NULL) { mm_queue_destroy(&logger->tasks); 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)); od_list_init(&slot->link); od_list_append(&logger->free_slots, &slot->link); } logger->free_slots_count = n; 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); mm_queue_destroy(&logger->tasks); return NOT_OK_RESPONSE; } return OK_RESPONSE; } int od_logger_open(od_logger_t *logger, char *path) { logger->fd = open(path, O_RDWR | O_CREAT | O_APPEND | O_CLOEXEC, 0644); if (logger->fd == -1) { return -1; } return 0; } int od_logger_reopen(od_logger_t *logger, char *path) { int old_fd = logger->fd; int rc = od_logger_open(logger, path); if (rc == -1) { logger->fd = old_fd; } else if (old_fd != -1) { close(old_fd); } return rc; } 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) { if (logger->fd != -1) { close(logger->fd); } logger->fd = -1; } 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; } __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 *format_pos = logger->format; char *format_end = logger->format + logger->format_len; char peer[128]; int len; while (format_pos < format_end) { if (*format_pos == '\\') { format_pos++; if (od_unlikely(format_pos == format_end)) { break; } if (od_unlikely((dst_end - dst_pos) < 1)) { break; } switch (*format_pos) { case '\\': dst_pos[0] = '\\'; dst_pos += 1; break; case 'n': dst_pos[0] = '\n'; dst_pos += 1; break; case 't': dst_pos[0] = '\t'; dst_pos += 1; break; case 'r': dst_pos[0] = '\r'; dst_pos += 1; 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 (*format_pos == '%') { format_pos++; if (od_unlikely(format_pos == format_end)) { break; } switch (*format_pos) { /* external_id */ case 'x': { if (client && client->external_id != NULL) { len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", client->external_id); dst_pos += len; break; } /* fall through fix (if client is not defined will write 'none' to log file) */ len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; } /* unixtime */ case 'n': { time_t tm = time(NULL); len = od_snprintf(dst_pos, dst_end - dst_pos, "%lu", tm); dst_pos += len; break; } /* timestamp */ case 't': { struct timeval tv; gettimeofday(&tv, NULL); struct tm tm; len = strftime(dst_pos, dst_end - dst_pos, "%FT%TZ", gmtime_r(&tv.tv_sec, &tm)); dst_pos += len; break; } /* millis */ case 'e': { struct timeval tv; gettimeofday(&tv, NULL); len = od_snprintf(dst_pos, dst_end - dst_pos, "%03d", (signed)tv.tv_usec / 1000); dst_pos += len; break; } /* pid */ case 'p': len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", logger->pid->pid_sz); dst_pos += len; break; /* thread id */ case 'T': len = od_snprintf(dst_pos, dst_end - dst_pos, "0x%llx", pthread_self()); dst_pos += len; break; /* client id */ case 'i': if (client && client->id.id_prefix != NULL) { len = od_snprintf( dst_pos, dst_end - dst_pos, "%s%.*s", client->id.id_prefix, (signed)sizeof(client->id.id), client->id.id); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; /* server id */ case 's': if (server && server->id.id_prefix != NULL) { len = od_snprintf( dst_pos, dst_end - dst_pos, "%s%.*s", server->id.id_prefix, (signed)sizeof(server->id.id), server->id.id); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; /* user name */ case 'u': if (client && client->startup.user.value_len) { len = od_snprintf( dst_pos, dst_end - dst_pos, "%s", client->startup.user.value); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; /* database name */ case 'd': if (client && client->startup.database.value_len) { len = od_snprintf( dst_pos, dst_end - dst_pos, "%s", client->startup.database.value); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; /* context */ case 'c': len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", context); dst_pos += len; break; /* level */ case 'l': len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", od_log_level[level]); dst_pos += len; break; /* message */ case 'm': len = od_vsnprintf(dst_pos, dst_end - dst_pos, fmt, args); dst_pos += len; break; /* message (escaped) */ case 'M': len = od_logger_escape( dst_pos, dst_end - dst_pos, fmt, args); dst_pos += len; break; /* server host */ case 'H': if (client && client->route) { od_route_t *route_ref = client->route; len = od_snprintf( dst_pos, dst_end - dst_pos, "%s", route_ref->rule->storage->host); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; /* client host */ case 'h': if (client && client->io.io) { od_getpeername(client->io.io, peer, sizeof(peer), 1, 0); len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", peer); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; /* client port */ case 'r': if (client && client->io.io) { od_getpeername(client->io.io, peer, sizeof(peer), 0, 1); len = od_snprintf(dst_pos, dst_end - dst_pos, "%s", peer); dst_pos += len; break; } len = od_snprintf(dst_pos, dst_end - dst_pos, "none"); dst_pos += len; break; case '%': if (od_unlikely((dst_end - dst_pos) < 1)) { break; } dst_pos[0] = '%'; dst_pos += 1; 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++; } /* 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 rc; if (l->fd != -1) { rc = writev(l->fd, iovecs, n); } 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 rc; if (l->fd != -1) { rc = write(l->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 %" PRIu64, msg_allocated, msg_cache_count, msg_cache_gc_count, msg_cache_size, count_coroutine, count_coroutine_cache, atomic_load(&logger->dropped_lines), mm_queue_size(&logger->tasks)); } void od_logger_stat(od_logger_t *logger) { log_machine_stats(logger); } static inline void od_logger(void *arg) { od_logger_t *logger = arg; atomic_store(&logger->loaded, 1); while (atomic_load(&logger->loaded) == 1) { static od_logger_slot_t *slot_buf[IOV_MAX]; static struct iovec iovecs[IOV_MAX]; size_t nmsg = mm_queue_pop_batch(&logger->tasks, slot_buf, IOV_MAX); 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); pthread_spin_lock(&logger->free_slots_lock); for (size_t i = 0; i < nmsg; ++i) { od_list_append(&logger->free_slots, &(slot_buf[i]->link)); } logger->free_slots_count += nmsg; pthread_spin_unlock(&logger->free_slots_lock); if (mm_queue_size(&logger->tasks) == 0) { mm_wait_list_wait(&logger->notifier, NULL, 1000); } } } void od_logger_shutdown(od_logger_t *logger) { atomic_store(&logger->loaded, 0); } 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); mm_queue_destroy(&logger->tasks); 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%llx", 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) { dst = od_logger_json_add_string( dst, dst_end, "server_host", client->route->rule->storage->host, 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(); } if (logger->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 async = atomic_load(&logger->loaded); if (async) { pthread_spin_lock(&logger->free_slots_lock); if (logger->free_slots_count > 0) { od_list_t *i = od_list_pop(&logger->free_slots); async_slot = od_container_of(i, od_logger_slot_t, link); logger->free_slots_count--; } pthread_spin_unlock(&logger->free_slots_lock); if (async_slot == NULL) { /* silently drop lines for overloaded logger */ atomic_fetch_add(&logger->dropped_lines, 1); 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; if (mm_queue_push(&logger->tasks, &async_slot) == 1) { /* we are the first who put the message, lets signal the logger thread */ mm_wait_list_notify(&logger->notifier); } } else { _od_logger_write(logger, output, len, level); } } odyssey-1.5.1-rc8/sources/machinarium/000077500000000000000000000000001517700303500176705ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/machinarium/README.md000066400000000000000000000047341517700303500211570ustar00rootroot00000000000000## 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.5.1-rc8/sources/machinarium/accept.c000066400000000000000000000037061517700303500213010ustar00rootroot00000000000000/* * 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_errno_retryable(err)) { /* wait for EPOLLIN event on socket */ rc = mm_io_wait_deadline(io); 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.5.1-rc8/sources/machinarium/backtrace.c000066400000000000000000000024231517700303500217540ustar00rootroot00000000000000#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.5.1-rc8/sources/machinarium/benchmark/000077500000000000000000000000001517700303500216225ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/machinarium/benchmark/Makefile000066400000000000000000000013761517700303500232710ustar00rootroot00000000000000CC = 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.5.1-rc8/sources/machinarium/benchmark/benchmark_channel.c000066400000000000000000000023261517700303500254130ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/benchmark/benchmark_channel_shared.c000066400000000000000000000022401517700303500267340ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/benchmark/benchmark_csw.c000066400000000000000000000015211517700303500245730ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/benchmark/benchmark_csw2.c000066400000000000000000000021171517700303500246570ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/benchmark/benchmark_msg_alloc.c000066400000000000000000000023701517700303500257420ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/bind.c000066400000000000000000000020751517700303500207540ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/machinarium/call.c000066400000000000000000000034041517700303500207500ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/cert_hash.c000066400000000000000000000005711517700303500217770ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/channel.c000066400000000000000000000114151517700303500214460ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/channel_api.c000066400000000000000000000035301517700303500222760ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/clock.c000066400000000000000000000111141517700303500211250ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include static int mm_clock_cmp(mm_timer_t *a, mm_timer_t *b) { if (a->timeout == b->timeout) { if (a->seq == b->seq) { assert(a == b); return 0; } return (a->seq > b->seq) ? 1 : -1; } else if (a->timeout > b->timeout) { return 1; } return -1; } void mm_clock_init(mm_clock_t *clock) { mm_buf_init(&clock->timers); clock->timers_count = 0; 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) { mm_buf_free(&clock->timers); } /* * Binary search routine for timer in sorted array. * We expect that there is at most only one occurrence of timer in list. * If there is not timer in list, we must return first index greater than timer. */ static int mm_clock_get_insert_position(mm_timer_t **list, int count, mm_timer_t *timer) { int low = 0, high = count; while (low < high) { int med = low + (high - low) / 2; int cmp = mm_clock_cmp(timer, list[med]); if (cmp == 0) { return med; } if (cmp > 0) { low = med + 1; } else { high = med; } } return low; } #ifndef NDEBUG /* This function only used in asserts */ static int mm_clock_list_is_sorted(mm_timer_t **list, int count) { for (int i = 1; i < count; i++) { if (mm_clock_cmp(list[i - 1], list[i]) >= 0) { return 0; } } return 1; } #endif int mm_clock_timer_add(mm_clock_t *clock, mm_timer_t *timer) { int count = clock->timers_count; int rc; rc = mm_buf_ensure(&clock->timers, sizeof(mm_timer_t *)); if (rc == -1) { return -1; } mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; mm_buf_advance(&clock->timers, sizeof(mm_timer_t *)); timer->seq = clock->timers_seq++; timer->timeout = clock->time_ms + timer->interval; timer->active = 1; timer->clock = clock; int insert_position = mm_clock_get_insert_position(list, count, timer); /* We are last, or insert position is after us */ assert((insert_position == count) || mm_clock_cmp(list[insert_position], timer) > 0); memmove(list + insert_position + 1, list + insert_position, sizeof(mm_timer_t *) * (count - insert_position)); list[insert_position] = timer; clock->timers_count = count + 1; assert(mm_clock_list_is_sorted(list, clock->timers_count)); return 0; } int mm_clock_timer_del(mm_clock_t *clock, mm_timer_t *timer) { if (!timer->active) { return -1; } assert(clock->timers_count >= 1); mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; int i = mm_clock_get_insert_position(list, clock->timers_count, timer); /* We should find timer */ assert(list[i] == timer); for (; i < clock->timers_count; i++) { if (list[i] != timer) { continue; } memmove(list + i, list + i + 1, sizeof(mm_timer_t *) * (clock->timers_count - i - 1)); clock->timers.pos -= sizeof(mm_timer_t *); clock->timers_count -= 1; break; } assert(mm_clock_list_is_sorted(list, clock->timers_count)); timer->active = 0; return 0; } mm_timer_t *mm_clock_timer_min(mm_clock_t *clock) { if (clock->timers_count == 0) { return NULL; } mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; return list[0]; } int mm_clock_step(mm_clock_t *clock) { if (clock->timers_count == 0) { return 0; } mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; int timers_hit = 0; int i = 0; for (; i < clock->timers_count; i++) { mm_timer_t *timer = list[i]; if (timer->timeout > clock->time_ms) { break; } timer->callback(timer); timer->active = 0; timers_hit++; } if (!timers_hit) { return 0; } int timers_left = clock->timers_count - timers_hit; if (timers_left == 0) { mm_buf_reset(&clock->timers); clock->timers_count = 0; return timers_hit; } memmove(list, list + timers_hit, sizeof(mm_timer_t *) * timers_left); clock->timers.pos -= sizeof(mm_timer_t *) * timers_hit; clock->timers_count -= timers_hit; return timers_hit; } 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 uint32_t mm_clock_gettimeofday(void) { struct timeval tv; gettimeofday(&tv, NULL); return 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.5.1-rc8/sources/machinarium/close.c000066400000000000000000000010561517700303500211430ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/compression.c000066400000000000000000000042751517700303500224050ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/machinarium/cond.c000066400000000000000000000054721517700303500207670ustar00rootroot00000000000000 /* * 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_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]; 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.5.1-rc8/sources/machinarium/connect.c000066400000000000000000000033121517700303500214640ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/context.c000066400000000000000000000066631517700303500215330ustar00rootroot00000000000000 /* * 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 static 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)); } 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; memset(sp, 0, sizeof(void *) * 6); #elif __aarch64__ /* for aarch64 we need to place return address in x30 reg */ sp -= 16; memset(sp, 0, sizeof(void *) * 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; memset(sp, 0, sizeof(void *) * 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.5.1-rc8/sources/machinarium/context_stack.c000066400000000000000000000020101517700303500226760ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/context_swap_aarch64.S000066400000000000000000000011751517700303500240460ustar00rootroot00000000000000.text .global mm_context_swap_impl mm_context_swap_impl: stp x8, x16, [sp, #-16]! stp x17, x18, [sp, #-16]! stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! stp x23, x24, [sp, #-16]! stp x25, x26, [sp, #-16]! stp x27, x28, [sp, #-16]! stp x29, x30, [sp, #-16]! mov x3, sp str x3, [x0] ldr x3, [x1] mov sp, x3 ldp x29, x30, [sp], #16 ldp x27, x28, [sp], #16 ldp x25, x26, [sp], #16 ldp x23, x24, [sp], #16 ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ldp x17, x18, [sp], #16 ldp x8, x16, [sp], #16 ret x30 .section .note.GNU-stack,"",@progbitsodyssey-1.5.1-rc8/sources/machinarium/context_swap_x32.S000066400000000000000000000004601517700303500232260ustar00rootroot00000000000000.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.5.1-rc8/sources/machinarium/context_swap_x64.S000066400000000000000000000005531517700303500232360ustar00rootroot00000000000000.text .globl mm_context_swap_impl .type mm_context_swap_impl, @function mm_context_swap_impl: pushq %rbp pushq %rbx pushq %r12 pushq %r13 pushq %r14 pushq %r15 movq %rsp, (%rdi) movq (%rsi), %rsp popq %r15 popq %r14 popq %r13 popq %r12 popq %rbx popq %rbp ret .section .note.GNU-stack,"",@progbits odyssey-1.5.1-rc8/sources/machinarium/coroutine.c000066400000000000000000000034331517700303500220460ustar00rootroot00000000000000 /* * 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; coroutine->io_count = 0; 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.5.1-rc8/sources/machinarium/coroutine_cache.c000066400000000000000000000035101517700303500231650ustar00rootroot00000000000000 /* * 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); return coroutine; } cache->count_total++; coroutine = mm_coroutine_allocate(cache->stack_size, cache->stack_size_guard); if (coroutine == NULL) { cache->count_total--; } 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.5.1-rc8/sources/machinarium/dns.c000066400000000000000000000027751517700303500206330ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include typedef struct { char *addr; char *service; struct addrinfo *hints; struct addrinfo **res; int rc; } mm_getaddrinfo_t; static void mm_getaddrinfo_cb(void *arg) { mm_getaddrinfo_t *gai = arg; gai->rc = mm_socket_getaddrinfo(gai->addr, gai->service, gai->hints, gai->res); } MACHINE_API int machine_getaddrinfo(char *addr, char *service, struct addrinfo *hints, struct addrinfo **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.5.1-rc8/sources/machinarium/ds/000077500000000000000000000000001517700303500202765ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/machinarium/ds/hm.c000066400000000000000000000164411517700303500210540ustar00rootroot00000000000000#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 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_bucket_t *b) { mm_list_init(&b->kvps); } static void init_buckets(mm_hashmap_t *hm) { for (size_t i = 0; i < hm->nbuckets; ++i) { init_bucket(&hm->buckets[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 == 0) { nlocks = 1; } 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; } hm->locks = mm_malloc(sizeof(mm_mutex_t) * nlocks); if (hm->locks == NULL) { mm_free(hm->buckets); mm_free(hm); return 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_buckets(hm); init_locks(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]; mm_mutex_t *mu = &hm->locks[lock_idx(hm, bi)]; mm_mutex_lock2(mu); 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(kvp, argv); if (rc != 0) { break; } } mm_mutex_unlock(mu); 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 create) { memset(klock, 0, sizeof(mm_hashmap_keylock_t)); mm_hash_t hash = hm->khash(key); size_t bidx = bucket_idx(hm, hash); size_t lidx = lock_idx(hm, bidx); mm_mutex_t *mu = &hm->locks[lidx]; mm_hashmap_bucket_t *bucket = &hm->buckets[bidx]; mm_mutex_lock2(mu); klock->mu = mu; mm_hashmap_kvp_t *kvp = kvp_find_locked(hm, bucket, hash, key); if (kvp != NULL) { klock->found = 1; klock->kvp = kvp; return 0; } mm_mutex_unlock(mu); if (!create) { klock->found = 0; klock->kvp = NULL; return 0; } mm_hashmap_kvp_t *newkvp = kvp_create(hm, hash, key); if (newkvp == NULL) { return -1; } mm_mutex_lock2(mu); 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; mm_mutex_unlock(klock->mu); } 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; } mm_mutex_unlock(klock->mu); } odyssey-1.5.1-rc8/sources/machinarium/ds/queue.c000066400000000000000000000054761517700303500216020ustar00rootroot00000000000000#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) { pthread_spin_lock(&q->lock); } static inline void queue_unlock(mm_queue_t *q) { pthread_spin_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)); pthread_spin_init(&q->lock, PTHREAD_PROCESS_PRIVATE); capacity = next_pow2(capacity); q->cap = capacity; q->cap_bytes = capacity * elsize; q->dtor = dtor; q->elsize = elsize; q->mask = q->cap_bytes - 1; q->buf = mm_malloc(q->cap_bytes); if (q->buf == NULL) { return -1; } memset(q->buf, 0, q->cap_bytes); 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->dtor(val); q->head += q->elsize; q->head &= q->mask; --q->size; } } pthread_spin_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]; memcpy(dst, val, q->elsize); q->tail += q->elsize; 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]; memcpy(dst, val, q->elsize); q->tail += q->elsize; 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]; memcpy(val, src, q->elsize); q->head += q->elsize; 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); total = q->size; if (total > max) { total = max; } size_t total_bytes = total * q->elsize; size_t to_end = q->cap_bytes - q->head; const void *src = &q->buf[q->head]; if (total_bytes <= to_end) { /* [ . . . head . . . . last . . . ] */ memcpy(dst, src, total_bytes); } else { /* [ . . last . . . . head . . . ] */ memcpy(dst, src, to_end); memcpy((uint8_t *)dst + to_end, q->buf, total_bytes - to_end); } q->head += total_bytes; q->head &= 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.5.1-rc8/sources/machinarium/ds/vector.c000066400000000000000000000030541517700303500217460ustar00rootroot00000000000000#include #include #include #include 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; } 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 ? 16 : 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.5.1-rc8/sources/machinarium/ds/vrb.c000066400000000000000000000127061517700303500212410ustar00rootroot00000000000000#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); } 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 = memfd_create("virtual-ring-buffer", MFD_CLOEXEC); 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; pthread_spin_init(&cache->lock, PTHREAD_PROCESS_PRIVATE); 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); pthread_spin_destroy(&cache->lock); } mm_virtual_rbuf_t *mm_virtual_rbuf_cache_get(mm_virtual_rbuf_cache_t *cache) { mm_virtual_rbuf_t *vrb = NULL; pthread_spin_lock(&cache->lock); if (cache->count > 0) { --cache->count; vrb = cache->rbufs[cache->count]; cache->rbufs[cache->count] = NULL; } pthread_spin_unlock(&cache->lock); 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) { pthread_spin_lock(&cache->lock); if (cache->count == cache->max) { mm_virtual_rbuf_free(vrb); pthread_spin_unlock(&cache->lock); return; } cache->rbufs[cache->count] = vrb; cache->count++; pthread_spin_unlock(&cache->lock); } odyssey-1.5.1-rc8/sources/machinarium/epoll.c000066400000000000000000000114071517700303500211520ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #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.5.1-rc8/sources/machinarium/event_mgr.c000066400000000000000000000062211517700303500220230ustar00rootroot00000000000000 /* * 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; rc = read(mgr->fd.fd, &id, sizeof(id)); (void)rc; assert(rc == sizeof(id)); /* 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)); mgr->fd.fd = mm_socket_eventfd(0); 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); 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); 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 = mgr->fd.fd; } 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.5.1-rc8/sources/machinarium/gdb/000077500000000000000000000000001517700303500204245ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/machinarium/gdb/README.md000066400000000000000000000107461517700303500217130ustar00rootroot00000000000000# 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.5.1-rc8/sources/machinarium/gdb/machinarium-gdb.py000066400000000000000000000661621517700303500240400ustar00rootroot00000000000000import 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" 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() 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, } 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 unwind_info = pending_frame.create_unwind_info( MMFrameId(self.registers['rsp'], self.registers['rip'])) 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_x64(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): if gdb_get_current_platform() != 'i386:x86-64': # To perform such things in other platforms # mm_get_pc_and_sp_for_coroutine_x64 should be rewritten for desired platform gdb.write( f"!!! Warning: current 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") MMCoroutines() MMCoroutineCmd() IgnoreErrorsCmd() ODListPrint() ODListPrintSelect() ODGetFieldOffset() ODClientCoroutines() ODListCurrentServers() gdb.write('done.\n', stream=gdb.STDLOG) odyssey-1.5.1-rc8/sources/machinarium/io.c000066400000000000000000000306151517700303500204500ustar00rootroot00000000000000 /* * 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; 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; fd = mm_socket(sa->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); 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_scheduler_register_io(); 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_scheduler_register_io(); 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) { return rc; } if (rc < 0) { int errno_ = errno; mm_errno_set(errno_); if (machine_errno_retryable(errno_)) { return -1; } } /* error of eof */ io->connected = 0; return rc; } int mm_io_read_pending(mm_io_t *io) { if (mm_tls_is_active(io)) { return mm_tls_read_pending(io); } 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; } int mm_io_wait_deadline(mm_io_t *io) { assert(io->deadline_ms > 0); uint64_t now_ms = machine_time_ms(); if (now_ms >= io->deadline_ms) { mm_errno_set(ETIMEDOUT); return MM_COND_WAIT_FAIL; } uint32_t left_ms; if (io->deadline_ms != UINT64_MAX) { if (io->deadline_ms - now_ms >= UINT32_MAX) { left_ms = UINT32_MAX; } else { left_ms = (uint32_t)(io->deadline_ms - now_ms); } } else { left_ms = UINT32_MAX; } 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; pfd.events = POLLERR | POLLHUP | POLLRDHUP; 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; int close = events & POLLHUP || events & POLLRDHUP; if (err && fd->mask & MM_ERR) { fd->on_err(&io->handle); } if (close && fd->mask & MM_CLOSE) { fd->on_close(&io->handle); } return 0; } odyssey-1.5.1-rc8/sources/machinarium/loop.c000066400000000000000000000026161517700303500210120ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include int mm_loop_init(mm_loop_t *loop) { loop->poll = mm_epoll_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.5.1-rc8/sources/machinarium/lrand48.c000066400000000000000000000053101517700303500213070ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/machinarium/machine.c000066400000000000000000000212731517700303500214450ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #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, &mm_self->coroutine_cache); 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->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; SSL_CTX_free(ctx_container->tls_ctx); mm_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: %ld (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(machine->main, machine->main_arg); (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); } free_tls_container(machine->client_tls_ctx); free_tls_container(machine->server_tls_ctx); 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; #ifdef MM_MEM_PROF machine->allocated_bytes = 0; machine->freed_bytes = 0; #endif 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_coroutine_cache_init(&machine->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, 3 * PTHREAD_STACK_MIN, 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; } static inline int machine_wait_internal(uint64_t machine_id, int (*awaiter)(mm_thread_t *)) { mm_machine_t *machine; machine = mm_machinemgr_delete_by_id(&machinarium.machine_mgr, machine_id); if (machine == NULL) { return -1; } int rc; rc = awaiter(&machine->thread); if (machine->name) { mm_free(machine->name); } mm_free(machine); return rc; } MACHINE_API int machine_wait(uint64_t machine_id) { return machine_wait_internal(machine_id, mm_thread_join); } int machine_wait_nb(uint64_t machine_id) { return machine_wait_internal(machine_id, mm_thread_join_nb); } 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); } static inline mm_coroutine_t * mm_coroutine_create_internal(machine_coroutine_t function, void *arg) { mm_errno_set(0); mm_coroutine_t *coroutine; coroutine = mm_coroutine_cache_pop(&mm_self->coroutine_cache); if (coroutine == NULL) { mm_errno_set(ENOMEM); return NULL; } mm_scheduler_new(&mm_self->scheduler, coroutine, function, arg); 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); 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); if (coroutine == NULL) { return -1; } mm_coroutine_set_name(coroutine, name); 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); 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; } 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 uint32_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) { mm_coroutine_cache_stat(&mm_self->coroutine_cache, coroutine_count, coroutine_cache_count); mm_msgcache_stat(&mm_self->msg_cache, msg_allocated, msg_cache_gc_count, msg_cache_count, msg_cache_size); } odyssey-1.5.1-rc8/sources/machinarium/machine_mgr.c000066400000000000000000000033601517700303500223070ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include void mm_machinemgr_init(mm_machinemgr_t *mgr) { pthread_spin_init(&mgr->lock, PTHREAD_PROCESS_PRIVATE); mm_list_init(&mgr->list); mgr->count = 0; mgr->seq = 0; } void mm_machinemgr_free(mm_machinemgr_t *mgr) { pthread_spin_destroy(&mgr->lock); } int mm_machinemgr_count(mm_machinemgr_t *mgr) { int count; pthread_spin_lock(&mgr->lock); count = mgr->count; pthread_spin_unlock(&mgr->lock); return count; } void mm_machinemgr_add(mm_machinemgr_t *mgr, mm_machine_t *machine) { pthread_spin_lock(&mgr->lock); machine->id = mgr->seq++; mm_list_append(&mgr->list, &machine->link); mgr->count++; pthread_spin_unlock(&mgr->lock); } void mm_machinemgr_delete(mm_machinemgr_t *mgr, mm_machine_t *machine) { pthread_spin_lock(&mgr->lock); mm_list_unlink(&machine->link); mgr->count--; pthread_spin_unlock(&mgr->lock); } mm_machine_t *mm_machinemgr_delete_by_id(mm_machinemgr_t *mgr, uint64_t id) { pthread_spin_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--; pthread_spin_unlock(&mgr->lock); return machine; } } pthread_spin_unlock(&mgr->lock); return NULL; } mm_machine_t *mm_machinemgr_find_by_id(mm_machinemgr_t *mgr, uint64_t id) { pthread_spin_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) { pthread_spin_unlock(&mgr->lock); return machine; } } pthread_spin_unlock(&mgr->lock); return NULL; } odyssey-1.5.1-rc8/sources/machinarium/memory.c000066400000000000000000000071731517700303500213540ustar00rootroot00000000000000#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.5.1-rc8/sources/machinarium/mm.c000066400000000000000000000035251517700303500204520ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include static int machinarium_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_initialized = 0; 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_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 int machinarium_init(void) { if (machinarium_initialized) { return -1; } if (machinarium_stack_size <= 0) { machinarium_stack_size = 4; } if (machinarium_pool_size == 0) { machinarium_pool_size = 1; } machinarium.config.page_size = machinarium_page_size(); machinarium.config.stack_size = machinarium_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; 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_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.5.1-rc8/sources/machinarium/msg.c000066400000000000000000000047661517700303500206370ustar00rootroot00000000000000 /* * 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 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.5.1-rc8/sources/machinarium/msg_cache.c000066400000000000000000000032741517700303500217530ustar00rootroot00000000000000 /* * 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_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) { if (msg->machine_id != mm_self->id || mm_buf_size(&msg->data) > cache->gc_watermark) { 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.5.1-rc8/sources/machinarium/mutex.c000066400000000000000000000044041517700303500212000ustar00rootroot00000000000000/* * 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); atomic_init(&mutex->owner_machine, (uintptr_t)NULL); atomic_init(&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)) { atomic_store(&mutex->owner_machine, (uintptr_t)mm_self); atomic_store(&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(); } assert(atomic_load(&mutex->owner_machine) == (uintptr_t)mm_self); assert(atomic_load(&mutex->owner_coro_id) == mm_self->scheduler.current->id); atomic_store(&mutex->owner_machine, (uintptr_t)NULL); atomic_store(&mutex->owner_coro_id, MM_SLEEPY_NO_CORO_ID); atomic_store(&mutex->state, MM_MUTEX_UNLOCKED); mm_wait_list_notify(&mutex->wl); } odyssey-1.5.1-rc8/sources/machinarium/read.c000066400000000000000000000035001517700303500207450ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/ring_buffer.c000066400000000000000000000137111517700303500223270ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/machinarium/scheduler.c000066400000000000000000000121151517700303500220120ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #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, mm_coroutine_cache_t *cache) { 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(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: %ld; coroutine ID: %ld", 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; current->io_count = 0; 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); } void mm_scheduler_register_io(void) { mm_scheduler_t *sched = &mm_self->scheduler; mm_coroutine_t *coro = sched->current; if (mm_unlikely(coro == NULL)) { return; } ++coro->io_count; /* * not so often situation, but if some coro performs too much * io on one 'time slice' - lets do a little punishment */ if (coro->io_count > 10) { machine_sleep(0); } } odyssey-1.5.1-rc8/sources/machinarium/shutdown.c000066400000000000000000000006721517700303500217140ustar00rootroot00000000000000 #include #include #include 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_shutdown_receptions(mm_io_t *io) { mm_errno_set(0); int rc = shutdown(io->fd, SHUT_RD); if (rc == -1) { return MM_NOTOK_RETCODE; } return MM_OK_RETCODE; } odyssey-1.5.1-rc8/sources/machinarium/signal_mgr.c000066400000000000000000000052021517700303500221550ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #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); } } 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(); } 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.5.1-rc8/sources/machinarium/socket.c000066400000000000000000000125101517700303500213230ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include #include #include #include #include #include #include #include int mm_socket(int domain, int type, int protocol) { /* get and return file descriptor of env socket */ int fd; fd = socket(domain, type | SOCK_CLOEXEC, protocol); return fd; } int mm_socket_eventfd(unsigned int initval) { int rc; rc = eventfd(initval, EFD_NONBLOCK | EFD_CLOEXEC); return rc; } 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, 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; } rc = setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &usr_timeout, sizeof(usr_timeout)); if (rc == MM_NOTOK_RETCODE) { return MM_NOTOK_RETCODE; } } 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) rc = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)); if (rc == -1) { return -1; } #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; rc = accept4(fd, sa, slen, SOCK_CLOEXEC | SOCK_NONBLOCK); 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 rc; rc = ioctl(fd, FIONREAD, &rc); if (rc == -1) { return -1; } return rc > 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.5.1-rc8/sources/machinarium/stat.c000066400000000000000000000007241517700303500210120ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/task_mgr.c000066400000000000000000000051541517700303500216500ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include #include enum { MM_TASK, MM_TASK_EXIT }; static void mm_taskmgr_main(void *arg __attribute__((unused))) { sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_BLOCK, &mask, NULL); 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(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); } } } 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) { mgr->workers_count = workers_count; mgr->workers = mm_malloc(sizeof(int) * 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] = machine_create(name, mm_taskmgr_main, NULL); } 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]); 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.5.1-rc8/sources/machinarium/thread.c000066400000000000000000000025671517700303500213150ustar00rootroot00000000000000 /* * machinarium. * * cooperative multitasking engine. */ #include #include #include #include int mm_thread_create(mm_thread_t *thread, int 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; } 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_join_nb(mm_thread_t *thread) { /* * non-blocking version of thread joining * TODO: rewrite to smth without polling */ int rc; while (1) { rc = pthread_tryjoin_np(thread->id, NULL); if (rc == 0) { break; } if (rc == EBUSY) { machine_sleep(100); continue; } return rc; } return rc; } int mm_thread_set_name(char *name) { int rc; rc = pthread_setname_np(pthread_self(), name); 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.5.1-rc8/sources/machinarium/tls.c000066400000000000000000000473771517700303500206600ustar00rootroot00000000000000 /* * 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 */ SSL_CTX *mm_tls_get_context(mm_io_t *io, int is_client) { mm_tls_ctx_t *ctx_container; if (is_client) { ctx_container = mm_self->client_tls_ctx; } else { ctx_container = mm_self->server_tls_ctx; } while (ctx_container != NULL) { if (ctx_container->key == io->tls) { return ctx_container->tls_ctx; } ctx_container = ctx_container->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 cache */ ctx_container = mm_malloc(sizeof(*ctx_container)); if (ctx_container == NULL) { goto error; } ctx_container->key = io->tls; ctx_container->tls_ctx = ctx; if (is_client) { ctx_container->next = mm_self->client_tls_ctx; mm_self->client_tls_ctx = ctx_container; } else { ctx_container->next = mm_self->server_tls_ctx; mm_self->server_tls_ctx = 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; X509_NAME *subject_name = NULL; char *common_name = NULL; int common_name_len = 0; 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; } common_name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, NULL, 0); if (common_name_len < 0) { mm_tls_error(io, 0, "X509_NAME_get_text_by_NID()"); goto error; } common_name = mm_calloc(common_name_len + 1, 1); if (common_name == NULL) { mm_tls_error(io, 0, "memory allocation failed"); goto error; } X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, common_name_len + 1); /* validate name */ if (common_name_len != (int)strlen(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(common_name, name) == -1) { mm_tls_error(io, 0, "bad common name: %s (expected %s)", common_name, name); goto error; } X509_free(cert); mm_free(common_name); return 0; error: X509_free(cert); if (common_name) { mm_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; } } rc = SSL_get_verify_result(io->tls_ssl); if (rc != X509_V_OK) { switch (rc) { case 2: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to get issuer certificate"); break; case 3: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to get certificate CRL"); break; case 4: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to decrypt certificate's signature"); break; case 5: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to decrypt CRL's signature"); break; case 6: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to decode issuer public key"); break; case 7: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate signature failure"); break; case 8: mm_tls_error( io, 0, "SSL_get_verify_result(): CRL signature failure"); break; case 9: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate is not yet valid"); break; case 10: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate has expired"); break; case 11: mm_tls_error( io, 0, "SSL_get_verify_result(): CRL is not yet valid"); break; case 12: mm_tls_error( io, 0, "SSL_get_verify_result(): CRL has expired"); break; case 13: mm_tls_error( io, 0, "SSL_get_verify_result(): format error in certificate's notBefore field"); break; case 14: mm_tls_error( io, 0, "SSL_get_verify_result(): format error in certificate's notAfter field"); break; case 15: mm_tls_error( io, 0, "SSL_get_verify_result(): format error in CRL's lastUpdate field"); break; case 16: mm_tls_error( io, 0, "SSL_get_verify_result(): format error in CRL's nextUpdate field"); break; case 17: mm_tls_error( io, 0, "SSL_get_verify_result(): out of memory"); break; case 18: mm_tls_error( io, 0, "SSL_get_verify_result(): self signed certificate"); break; case 19: mm_tls_error( io, 0, "SSL_get_verify_result(): self signed certificate in certificate chain"); break; case 20: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to get local issuer certificate"); break; case 21: mm_tls_error( io, 0, "SSL_get_verify_result(): unable to verify the first certificate"); break; case 22: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate chain too long"); break; case 23: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate revoked"); break; case 24: mm_tls_error( io, 0, "SSL_get_verify_result(): invalid CA certificate"); break; case 25: mm_tls_error( io, 0, "SSL_get_verify_result(): path length constraint exceeded"); break; case 26: mm_tls_error( io, 0, "SSL_get_verify_result(): unsupported certificate purpose"); break; case 27: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate not trusted"); break; case 28: mm_tls_error( io, 0, "SSL_get_verify_result(): certificate rejected"); break; case 29: mm_tls_error( io, 0, "SSL_get_verify_result(): subject issuer mismatch"); break; case 30: mm_tls_error( io, 0, "SSL_get_verify_result(): authority and subject key identifier mismatch"); break; case 31: mm_tls_error( io, 0, "SSL_get_verify_result(): authority and issuer serial number mismatch"); break; case 32: mm_tls_error( io, 0, "SSL_get_verify_result(): key usage does not include certificate signing"); break; case 50: mm_tls_error( io, 0, "SSL_get_verify_result(): application verification failure"); break; default: mm_tls_error( io, 0, "SSL_get_verify_result(): unknown"); } return -1; } } return 0; } int mm_tls_write(mm_io_t *io, const char *buf, int size) { mm_tls_error_reset(io); 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 || error == SSL_ERROR_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); 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 || error == SSL_ERROR_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); 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 || error == SSL_ERROR_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.5.1-rc8/sources/machinarium/wait_flag.c000066400000000000000000000046321517700303500217760ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/machinarium/wait_group.c000066400000000000000000000061271517700303500222220ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/machinarium/wait_list.c000066400000000000000000000105761517700303500220440ustar00rootroot00000000000000/* * machinarium. * * cooperative multitasking engine. */ #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) { 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); 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); 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); 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; } 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 = mm_eventmgr_signal(&sleepy->event); 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] = mm_eventmgr_signal(&sleepy->event); } 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; } odyssey-1.5.1-rc8/sources/machinarium/write.c000066400000000000000000000056151517700303500211750ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/machinarium/zpq_stream.c000066400000000000000000000356701517700303500222340ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/main.c000066400000000000000000000024571517700303500164730ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #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 */ int get_args_len_sum(int argc, char *argv[]) { /* * we will use argv[0] as a way to set proc title * and before write new title - previous content must be replace with * 0, otherwise, there might be some trash after result title */ int sum = 0; for (int i = 0; i < argc; ++i) { sum += strlen(argv[i]) + 1 /* 0-byte */; } return sum; } int main(int argc, char *argv[], char *envp[]) { init_tcmalloc_profile(); od_instance_t *odyssey = od_instance_create(); odyssey->orig_argv_ptr = argv[0]; odyssey->orig_argv_ptr_len = get_args_len_sum(argc, argv); int rc = od_instance_main(odyssey, argc, argv, envp); if (rc == -1) { rc = EXIT_FAILURE; } else { rc = EXIT_SUCCESS; } return rc; } odyssey-1.5.1-rc8/sources/mdb_iamproxy.c000066400000000000000000000200251517700303500202300ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/misc.c000066400000000000000000000052121517700303500164720ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/module.c000066400000000000000000000104011517700303500170200ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/multi_pool.c000066400000000000000000000157221517700303500177310ustar00rootroot00000000000000/* * 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); pthread_spin_init(&mpool->lock, PTHREAD_PROCESS_PRIVATE); 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); } pthread_spin_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.5.1-rc8/sources/murmurhash.c000066400000000000000000000020001517700303500177220ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include /* from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash1.cpp */ /* * Copyright (C) Austin Appleby */ const od_hash_t seed = 0x5bd1e995; od_hash_t od_murmur_hash(const void *raw, size_t len) { const unsigned int m = 0xc6a4a793; const int r = 16; unsigned int h = seed ^ (len * m); unsigned int k; const unsigned char *data = (const unsigned char *)raw; char buf[4]; /* raw may be misaligned */ memcpy(buf, data, len >= 4 ? 4 : len); while (len >= 4) { k = *(unsigned int *)buf; h += k; h *= m; h ^= h >> 16; data += 4; len -= 4; memcpy(buf, data, len >= 4 ? 4 : len); } /*---------- */ switch (len) { case 3: h += buf[2] << 16; break; case 2: h += buf[1] << 8; break; case 1: h += buf[0]; h *= m; h ^= h >> r; break; }; /*---------- */ h *= m; h ^= h >> 10; h *= m; h ^= h >> 17; return h; } odyssey-1.5.1-rc8/sources/od_memory.c000066400000000000000000000007421517700303500175340ustar00rootroot00000000000000#include #include #include void *od_malloc(size_t size) { return mm_malloc(size); } void od_free(void *ptr) { mm_free(ptr); } void *od_calloc(size_t nmemb, size_t size) { return mm_calloc(nmemb, size); } void *od_realloc(void *ptr, size_t size) { return mm_realloc(ptr, size); } char *od_strdup(const char *s) { return mm_strdup(s); } char *od_strndup(const char *s, size_t n) { return mm_strndup(s, n); } odyssey-1.5.1-rc8/sources/option.c000066400000000000000000000030161517700303500170470ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include const char *argp_program_version; const char *argp_program_bug_address = ""; 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) { od_dbg_printf_on_dvl_lvl( 1, "daemonize config opt is %d and console flag is %d, so daemonizing process\n", 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.5.1-rc8/sources/pam.c000066400000000000000000000062241517700303500163200ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/pid.c000066400000000000000000000020461517700303500163150ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/pool.c000066400000000000000000000047061517700303500165170ustar00rootroot00000000000000/* * 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->reset_timeout_ms = 1000; /* 1 sec */ pool->notice_after_waiting_ms = -1; pool->attach_check = 1; 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; } 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.5.1-rc8/sources/prom_metrics.c000066400000000000000000000305661517700303500202540ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/pstmt.c000066400000000000000000000221061517700303500167070ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * client hash map * "P_0" -> *od_prepared_stmt_t */ static mm_hash_t murmur_str_ptr(const void *data) { const char *ptr = *(const char **)data; return (mm_hash_t)od_murmur_hash(ptr, strlen(ptr)); } 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? */, 1 /* nlocks = 1, no fully-concurrent access to server hashmap */, sizeof(char *), sizeof(od_pstmt_t *), str_ptr_cmp, murmur_str_ptr, str_ptr_dtor, NULL /* no need to free the pointer from global table */, str_ptr_copy); } void od_client_pstmt_hashmap_free(mm_hashmap_t *hm) { 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, const od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(client->prep_stmt_ids, &klock, &name, 1 /* do create */); if (rc == -1) { /* cant create and lock is not held */ return rc; } int ret = 0; if (!klock.found || name[0] == '\0') { /* * new element - set the value * * if already exists - rewrite value only if name is "" * (its PG specific for "" statemnt - unnamed statemnt is * rewritten every Parse, not generate ErrorResponse about * statement already exists) */ void *val = mm_hashmap_kvp_val(client->prep_stmt_ids, klock.kvp); memcpy(val, &pstmt, sizeof(const od_pstmt_t *)); ret = 0; } else { /* value already exists and name != "" */ ret = 1; } /* no real concurrent access - can unlock now and return */ mm_hashmap_unlock_key(client->prep_stmt_ids, &klock); 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) { mm_hashmap_remove(client->prep_stmt_ids, &klock); } /* if not found - lock is not held */ return 0; } void od_client_pstmts_clear(od_client_t *client) { mm_hashmap_clear(client->prep_stmt_ids); } /* * server hashmap * "odyssey_pstmt_0" -> *od_prepared_stmt_t */ static mm_hash_t murmur_str_inplace(const void *data) { const char *key = data; return (mm_hash_t)od_murmur_hash(data, strlen(key)); } 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( 100 /* XXX: big enough? */, 1 /* nlocks = 1, no fully-concurrent access to server hashmap */, sizeof(od_pstmt_name_t), sizeof(od_pstmt_t *), str_inplace_cmp, murmur_str_inplace, 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_pstmt_hashmap_free(mm_hashmap_t *hm) { mm_hashmap_free(hm); } 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) { /* 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, const od_pstmt_t *pstmt) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(server->prep_stmts, &klock, pstmt->name, 1 /* do create */); if (rc == -1) { /* cant create and lock is not held */ return rc; } int ret = 0; if (!klock.found) { /* new element - set the value */ void *val = mm_hashmap_kvp_val(server->prep_stmts, klock.kvp); memcpy(val, &pstmt, sizeof(const od_pstmt_t *)); ret = 0; } else { 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) { mm_hashmap_remove(server->prep_stmts, &klock); } /* if not found - lock is not held */ return 0; } void od_server_pstmts_clear(od_server_t *server) { mm_hashmap_clear(server->prep_stmts); } /* * global map * od_pstmt_desc_t -> od_pstmt_t */ static mm_hash_t murmur_pstmt_desc(const void *data) { const od_pstmt_desc_t *desc = data; return (mm_hash_t)od_murmur_hash(desc->data, desc->len); } 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 void pstmt_desc_free(void *k) { od_pstmt_desc_t *desc = k; od_free(desc->data); } static int pstmt_desc_copy_cb(void *dest, const void *src) { const od_pstmt_desc_t *orig = src; od_pstmt_desc_t copy = od_pstmt_desc_copy(*orig); if (copy.data == NULL) { return 1; } memcpy(dest, ©, sizeof(od_pstmt_desc_t)); return 0; } mm_hashmap_t *od_global_pstmts_map_create(void) { return mm_hashmap_create(1000 /* XXX: big enough? */, 1000 /* XXX: big enough? */, sizeof(od_pstmt_desc_t), sizeof(od_pstmt_t), pstmt_desc_cmp, murmur_pstmt_desc, pstmt_desc_free, NULL, pstmt_desc_copy_cb); } void od_global_pstmts_map_free(mm_hashmap_t *hm) { mm_hashmap_free(hm); } od_pstmt_t *od_pstmt_create_or_get(mm_hashmap_t *pstmts, const od_pstmt_desc_t desc) { mm_hashmap_keylock_t klock; int rc = mm_hashmap_lock_key(pstmts, &klock, &desc, 1 /* create if not exists */); if (rc == -1) { return NULL; } void *value = mm_hashmap_kvp_val(pstmts, klock.kvp); if (!klock.found) { /* init new prep stmt */ od_pstmt_t pstmt; memset(&pstmt, 0, sizeof(od_pstmt_t)); od_pstmt_next_name(&pstmt); /* let the value points to key */ pstmt.desc = *((od_pstmt_desc_t *)mm_hashmap_kvp_key( pstmts, klock.kvp)); memcpy(value, &pstmt, sizeof(od_pstmt_t)); } mm_hashmap_unlock_key(pstmts, &klock); return value; } /* 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; } void od_pstmt_next_name(od_pstmt_t *out) { static atomic_uint_fast64_t cnt = 0; uint64_t num = atomic_fetch_add(&cnt, 1); od_snprintf(out->name, sizeof(od_pstmt_name_t), "%s%" PRIu64, OD_PSTMT_NAME_PREFIX, num); } odyssey-1.5.1-rc8/sources/query.c000066400000000000000000000054331517700303500167110ustar00rootroot00000000000000 /* * 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, char *query, char *param) { 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) == 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, UINT32_MAX); if (msg == NULL) { if (!machine_timedout()) { od_error(&instance->logger, context, server->client, server, "read error: %s", od_io_error(&server->io)); } 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); 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.5.1-rc8/sources/query_processing.c000066400000000000000000000057371517700303500211540ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include od_keyword_t od_query_process_keywords[] = { od_keyword("odyssey", OD_QUERY_PROCESSING_LODYSSEY), od_keyword("target_session_attrs", OD_QUERY_PROCESSING_LTSA), od_keyword("application_name", OD_QUERY_PROCESSING_LAPPNAME), od_keyword("set", OD_QUERY_PROCESSING_LSET), od_keyword("to", OD_QUERY_PROCESSING_LTO), od_keyword("show", OD_QUERY_PROCESSING_LSHOW), od_keyword("deallocate", OD_QUERY_PROCESSING_DEALLOCATE), od_keyword("prepare", OD_QUERY_PROCESSING_PREPARE), od_keyword("all", OD_QUERY_PROCESSING_ALL), { 0, 0, 0 } }; static int parse_deallocate_name(od_parser_t *parser, const od_token_t *nametok, const char **name, size_t *name_len) { od_token_t token; int rc = od_parser_next(parser, &token); int eof = rc == OD_PARSER_EOF; int semicolon = rc == OD_PARSER_SYMBOL && token.value.num == ';'; if (!eof && !semicolon) { /* some other bytes after DEALLOCATE [PREPARE] name */ return -1; } size_t tlen = (size_t)nametok->value.string.size; const char *tok = nametok->value.string.pointer; if (nametok->type == OD_PARSER_KEYWORD && tlen == 3 && strncasecmp(tok, "ALL", 3) == 0) { return 1; } *name = tok; *name_len = tlen; return 0; } static int parse_deallocate_impl(od_parser_t *parser, const char **name, size_t *name_len) { int rc; od_token_t token; od_keyword_t *keyword; /* * [PREPARE] name | ALL */ rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_KEYWORD: keyword = od_keyword_match(od_query_process_keywords, &token); /* DEALLOCATE pstmt */ if (keyword == NULL) { return parse_deallocate_name(parser, &token, name, name_len); } if (keyword->id == OD_QUERY_PROCESSING_ALL) { /* DEALLOCATE ALL */ return parse_deallocate_name(parser, &token, name, name_len); } else if (keyword->id == OD_QUERY_PROCESSING_PREPARE) { /* skip PREPARE, run again */ return parse_deallocate_impl(parser, name, name_len); } break; case OD_PARSER_STRING: /* DEALLOCATE 'pstmt' */ return parse_deallocate_name(parser, &token, name, name_len); default: return -1; } return -1; } int od_parse_deallocate_parser(od_parser_t *parser, const char **name, size_t *name_len) { return parse_deallocate_impl(parser, name, name_len); } int od_parse_deallocate(const char *query, size_t query_len, const char **name, size_t *name_len) { *name = NULL; *name_len = 0; od_parser_t parser; od_parser_init_queries_mode(&parser, query, query_len); od_token_t token; int rc = od_parser_next(&parser, &token); if (rc != OD_PARSER_KEYWORD) { return -1; } od_keyword_t *keyword; keyword = od_keyword_match(od_query_process_keywords, &token); if (keyword == NULL) { return -1; } if (keyword->id == OD_QUERY_PROCESSING_DEALLOCATE) { return parse_deallocate_impl(&parser, name, name_len); } return -1; } odyssey-1.5.1-rc8/sources/readahead.c000066400000000000000000000025341517700303500174410ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include static mm_virtual_rbuf_cache_t vrb_cache; static pthread_once_t vrb_cache_init_ctrl = PTHREAD_ONCE_INIT; static void destroy_vrb_cache(void) { mm_virtual_rbuf_cache_destroy(&vrb_cache); } static void init_vrb_cache(void) { size_t size = (size_t)od_global_get_instance()->config.cache_coroutine; int rc = mm_virtual_rbuf_cache_init(&vrb_cache, size); if (rc) { od_gfatal("readahead", NULL, NULL, "can't create vrb cache, errno = %d (%s)", machine_errno(), strerror(machine_errno())); abort(); } atexit(destroy_vrb_cache); } int od_readahead_prepare(od_readahead_t *readahead) { pthread_once(&vrb_cache_init_ctrl, init_vrb_cache); readahead->buf = mm_virtual_rbuf_cache_get(&vrb_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_put(&vrb_cache, readahead->buf); } } odyssey-1.5.1-rc8/sources/relay.c000066400000000000000000000367061517700303500166670ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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) { mm_vector_clear(&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; return mm_vector_append(&xbuf->msgs, &xmsg); } 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 process_virtual_set(od_client_t *client, od_parser_t *parser) { od_token_t token; od_keyword_t *keyword; const char *option_value; int option_value_len; int rc; od_server_t *server; od_instance_t *instance = client->global->instance; server = client->server; /* need to read exact odyssey parameter here */ rc = od_parser_next(parser, &token); if (rc != OD_PARSER_SYMBOL || token.value.num != '.') { return OD_OK; } rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_KEYWORD: keyword = od_keyword_match(od_query_process_keywords, &token); if (keyword == NULL || keyword->id != OD_QUERY_PROCESSING_LTSA) { /* some other option, skip */ return OD_OK; } break; default: return OD_OK; } rc = od_parser_next(parser, &token); if (rc != OD_PARSER_SYMBOL || token.value.num != '=') { return OD_OK; } rc = od_parser_next(parser, &token); if (rc != OD_PARSER_STRING) { return OD_OK; } option_value = token.value.string.pointer; option_value_len = token.value.string.size; /* for now, very straightforward logic, as there is only one supported param */ if (strncasecmp(option_value, "read-only", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, option_value, option_value_len); } else if (strncasecmp(option_value, "read-write", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, option_value, option_value_len); } else if (strncasecmp(option_value, "any", option_value_len) == 0) { kiwi_vars_set(&client->vars, KIWI_VAR_ODYSSEY_TARGET_SESSION_ATTRS, option_value, option_value_len); } else { /* some other option name, fallback to regular logic */ return OD_OK; } od_debug(&instance->logger, "virtual processing", client, server, "parsed tsa hint %.*s", option_value_len, option_value); machine_msg_t *msg = kiwi_be_write_command_complete(NULL, "SET"); 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_set_appname(od_client_t *client, od_parser_t *parser) { int rc; od_token_t token; rc = od_parser_next(parser, &token); switch (rc) { /* set application_name to ... */ case OD_PARSER_KEYWORD: { od_keyword_t *keyword = od_keyword_match(od_query_process_keywords, &token); if (keyword == NULL || keyword->id != OD_QUERY_PROCESSING_LTO) { goto error; } break; } /* set application_name = ... */ case OD_PARSER_SYMBOL: if (token.value.num != '=') { goto error; } break; default: goto error; } /* read original appname */ rc = od_parser_next(parser, &token); if (rc != OD_PARSER_STRING) { goto error; } char original_appname[64]; size_t len = (size_t)token.value.string.size > sizeof(original_appname) ? sizeof(original_appname) : (size_t)token.value.string.size; strncpy(original_appname, token.value.string.pointer, len); /* query should end with ; */ rc = od_parser_next(parser, &token); if (rc != OD_PARSER_SYMBOL || token.value.num != ';') { goto error; } rc = od_parser_next(parser, &token); if (rc != OD_PARSER_EOF) { goto error; } 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 = ", machine_errno()); goto error; } /* * done parsing - need to replace the message */ 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, od_parser_t *parser) { od_instance_t *instance = od_global_get_instance(); int rc; od_token_t token; od_keyword_t *keyword; /* need to read attribute name that are setting */ rc = od_parser_next(parser, &token); switch (rc) { case OD_PARSER_KEYWORD: keyword = od_keyword_match(od_query_process_keywords, &token); if (keyword == NULL) { /* some other option, skip */ return OD_OK; } if (keyword->id == OD_QUERY_PROCESSING_LAPPNAME) { /* this is set application_name ... query */ if (client->rule->application_name_add_host) { return process_set_appname(client, parser); } } if (keyword->id == OD_QUERY_PROCESSING_LODYSSEY) { /* * this is odyssey-specific virtual values set like * set odyssey.target_session_attr = 'read-only'; */ if (instance->config.virtual_processing) { int retstatus = process_virtual_set(client, parser); if (retstatus != OD_OK) { return retstatus; } } } break; default: return OD_OK; } return OD_OK; } static od_frontend_status_t process_vdeallocate(od_client_t *client, od_parser_t *parser) { od_instance_t *instance = client->global->instance; od_server_t *server = client->server; const char *command = NULL; char deallocate_name[128 /* NAMEDATALEN is 64 */] = { '\0' }; const char *name; size_t name_len; int rc = od_parse_deallocate_parser(parser, &name, &name_len); switch (rc) { case 1: od_debug(&instance->logger, "main", client, server, "DEALLOCATE ALL detected, remove from client hashmap"); od_client_pstmts_clear(client); command = "DEALLOCATE ALL"; break; case 0: strncpy(deallocate_name, name, od_min(name_len, sizeof(deallocate_name))); 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"; break; default: return OD_OK; } 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 try_virtual_process_query(od_client_t *client, char *query, uint32_t query_len) { od_instance_t *instance = od_global_get_instance(); int rc, need_process; od_parser_t parser; od_parser_init_queries_mode(&parser, query, query_len - 1 /* len is zero included */); od_token_t token; rc = od_parser_next(&parser, &token); /* all processed queries starts with show or set now */ if (rc != OD_PARSER_KEYWORD) { return OD_OK; } od_keyword_t *keyword; keyword = od_keyword_match(od_query_process_keywords, &token); if (keyword == NULL) { return OD_OK; } switch (keyword->id) { case OD_QUERY_PROCESSING_DEALLOCATE: /* DEALLOCATE name must be processed virtually */ need_process = client->rule->pool->reserve_prepared_statement; if (!need_process) { return OD_OK; } return process_vdeallocate(client, &parser); case OD_QUERY_PROCESSING_LSET: need_process = client->rule->application_name_add_host || instance->config.virtual_processing; if (!need_process) { return OD_OK; } return process_vset(client, &parser); case OD_QUERY_PROCESSING_LSHOW: /* fallthrough */ /* XXX: implement virtual show */ 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; } assert(client->server != NULL); /* to process the message, server was acquired, try again */ status = handler(relay, msg, timeout_ms); assert(status != OD_ATTACH); } return status; } static void process_discard(od_client_t *client, od_server_t *server, const char *query, size_t query_len) { od_route_t *route = client->route; od_instance_t *instance = client->global->instance; if (!route->rule->pool->reserve_prepared_statement) { return; } if (query_len >= 7 && strncmp(query, "DISCARD", 7) == 0) { od_debug(&instance->logger, "main", client, server, "discard detected, invalidate caches"); od_client_pstmts_clear(client); if (server != NULL) { od_server_pstmts_clear(server); } } } 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; } status = try_virtual_process_query(client, query, query_len); 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; } 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); 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); 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, query, query_len); } return status; } 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 (xbuf_append(&relay->xbuf, msg)) { status = OD_EOOM; goto to_return; } status = od_xplan_make_from_xbuf(&relay->xplan, relay); if (status != OD_OK) { goto to_return; } status = od_xplan_run(&relay->xplan, relay, timeout_ms); to_return: /* never reuse this ones */ xbuf_clear(&relay->xbuf); od_xplan_clear(&relay->xplan); 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; /* 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); /* * in vanilla PG, executing simple query removes the * unnamed pstmt on client */ if (relay->client->prep_stmt_ids != NULL) { od_client_remove_pstmt(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) { return process_possible_attach(execute_xbuf, relay, msg, timeout_ms); } od_frontend_status_t od_relay_process_xsync(od_relay_t *relay, machine_msg_t *msg, uint32_t timeout_ms) { return process_possible_attach(execute_xbuf, relay, msg, timeout_ms); } 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 (xbuf_append(&relay->xbuf, msg)) { return OD_EOOM; } return OD_OK; } odyssey-1.5.1-rc8/sources/reset.c000066400000000000000000000127471517700303500166740ustar00rootroot00000000000000 /* * 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; } /* 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, route->rule->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 = %d, sync_request = %d) - closing", server->sync_reply, server->sync_request); goto drop; } } /* Request one more sync point here. * In `od_server_synchronized` we * count number of sync/query msg send to connection * and number of RFQ received, if this numbers are equal, * we decide server connection as sync. However, this might be * not true, if client-server relay advanced some extended proto * msgs without sync. To safely execute discard queries, we need to * advadance sync point first. */ #ifdef FIX_ME_PLEASE if (od_backend_request_sync_point(server) == NOT_OK_RESPONSE) { goto error; } #endif 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; } 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; } } 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.5.1-rc8/sources/restart_sync.c000066400000000000000000000063311517700303500202620ustar00rootroot00000000000000 /* * 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.5.1-rc8/sources/route.c000066400000000000000000000112301517700303500166720ustar00rootroot00000000000000 /* * 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) { assert(route->exclusive_pool != NULL); 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, const od_address_t *address, od_server_t **server) { assert(route->exclusive_pool == NULL); 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); 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; } 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 (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; 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, const od_address_t *address, od_server_t **server) { *server = NULL; int rc; if (od_route_has_exclusive_pool(route)) { rc = pool_next_idle_exclusive_locked(route, address, server); } else { rc = pool_next_idle_shared_locked(route, address, 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); } odyssey-1.5.1-rc8/sources/router.c000066400000000000000000001002631517700303500170610ustar00rootroot00000000000000 /* * 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) { pthread_mutex_init(&router->lock, NULL); od_rules_init(&router->rules); od_list_init(&router->servers); od_route_pool_init(&router->route_pool); router->clients = 0; router->clients_routing = 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_route_pool_free(&router->route_pool); od_rules_free(&router->rules); pthread_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); assert(rule); 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_init(&added); od_list_init(&deleted); od_list_init(&to_drop); updates = od_rules_merge(&router->rules, rules, &added, &deleted, &to_drop); if (updates > 0) { od_extension_t *extensions = router->global->extensions; od_list_t *i; od_list_t *j; 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 config", NULL, NULL, "added rule: %s %s %s %s", rk->usr_name, rk->db_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 config", NULL, NULL, "deleted rule: %s %s %s %s", rk->usr_name, rk->db_name, rk->address_range.string_value, od_rule_conn_type_to_str(rk->conn_type)); } { 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_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_route_t *route = server->route; 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 > route->rule->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); /* 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); /* 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; } } od_rules_ref(rule); int allocated = od_router_keep_min_pool_size_for_route(route, 1); od_rules_unref(rule); 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 (!od_route_is_dynamic(route) && !route->rule->obsolete) { goto done; } /* remove route from route pool */ assert(pool->count > 0); pool->count--; od_list_unlink(&route->link); od_route_unlock(route); /* unref route rule and free route object */ od_rules_unref(route->rule); 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 */ assert(startup->database.value_len); 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 = 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; } if (startup->replication.value_len != 0) { if (strcmp(startup->replication.value, "database") == 0) { id.logical_rep = true; } else if (!parse_bool(startup->replication.value, &id.physical_rep)) { od_router_unlock(router); return OD_ROUTER_ERROR_REPLICATION; } } #ifdef LDAP_FOUND if (rule->ldap_storage_credentials_attr) { od_ldap_server_t *ldap_server = NULL; ldap_server = od_ldap_server_pull(&instance->logger, rule, false); if (ldap_server == NULL) { od_error(&instance->logger, "routing", client, NULL, "failed to get ldap connection"); od_router_unlock(router); return OD_ROUTER_ERROR_NOT_FOUND; } int ldap_rc = od_ldap_server_prepare(&instance->logger, ldap_server, rule, client); switch (ldap_rc) { case OK_RESPONSE: { od_ldap_endpoint_lock(rule->ldap_endpoint); ldap_server->idle_timestamp = (int)time(NULL); #if USE_POOL od_ldap_server_pool_set( rule->ldap_endpoint->ldap_search_pool, ldap_server, OD_SERVER_IDLE); #else od_ldap_server_free(ldap_server); #endif od_ldap_endpoint_unlock(rule->ldap_endpoint); 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); break; } case LDAP_INSUFFICIENT_ACCESS: { od_ldap_endpoint_lock(rule->ldap_endpoint); ldap_server->idle_timestamp = (int)time(NULL); #if USE_POOL od_ldap_server_pool_set( rule->ldap_endpoint->ldap_search_pool, ldap_server, OD_SERVER_IDLE); #else od_ldap_server_free(ldap_server); #endif od_ldap_endpoint_unlock(rule->ldap_endpoint); od_router_unlock(router); return OD_ROUTER_INSUFFICIENT_ACCESS; } default: { od_debug(&instance->logger, "routing", client, NULL, "closing bad ldap connection, need relogin"); od_ldap_endpoint_lock(rule->ldap_endpoint); #if USE_POOl od_ldap_server_pool_set( rule->ldap_endpoint->ldap_search_pool, ldap_server, OD_SERVER_UNDEF); #endif od_ldap_endpoint_unlock(rule->ldap_endpoint); od_ldap_server_free(ldap_server); 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; } } od_rules_ref(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_rules_unref(rule); od_route_unlock(route); od_router_unlock(router); /* * we assign client's rule to pass connection limit to the place where * error is handled Client does not actually belong to the pool */ client->rule = rule; 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->rule = rule; 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 */ assert(client->route); 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, const od_address_t *address, 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, 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)route->rule->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)od_atomic_u32_of(&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, address, &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; 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, const od_address_t *address) { od_server_t *server; od_route_t *route = client->route; od_route_lock(route); 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, address, &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, address, &server); if (st != OD_ROUTER_OK) { /* * od_router_try_create_new_server keeps lock held if everything ok * and unlocks it otherwise */ return st; } } assert(server != NULL); od_client_pool_set(&route->client_pool, client, OD_CLIENT_ACTIVE); od_server_attach_client(server, client); 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 (route->id.physical_rep || route->id.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"); uint64_t unused; return od_io_write_raw(&client->io, buf, n, &unused, 1000); } 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 %lu ms%s", time_spent, timeout ? ", timeout" : ""); int n = kiwi_be_format_notice(buf, sizeof(buf), 'M', msg); uint64_t unused; return od_io_write_raw(&client->io, buf, n, &unused, 1000); } od_router_status_t od_router_attach(od_router_t *router, od_client_t *client, bool wait_for_idle, const od_address_t *address) { od_route_t *route; od_router_status_t rc; uint64_t end_time_ms; route = client->route; 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 notice_sent = 0; int notice_sending = route->rule->pool->notice_after_waiting_ms >= 0; 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, address); if (rc != OD_ROUTER_NEED_WAIT) { /* ok or some other error */ status = rc; break; } 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, 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 (!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; 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; assert(server != NULL); assert(od_server_synchronized(server)); od_io_detach(&server->io); od_route_lock(route); int is_repl = route->id.physical_rep || route->id.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; 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); 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_copy(route->rule->storage); if (cancel->storage == NULL) { od_route_unlock(route); return -1; } 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.5.1-rc8/sources/rules.c000066400000000000000000001753261517700303500167070ustar00rootroot00000000000000 /* * 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) { pthread_mutex_init(&rules->mu, NULL); od_list_init(&rules->storages); #ifdef LDAP_FOUND od_list_init(&rules->ldap_endpoints); #endif od_list_init(&rules->rules); rules->next_order = 0; rules->destroy_flag = machine_wait_flag_create(); if (rules->destroy_flag == NULL) { /* TODO: do not abort here, should return some error code */ abort(); } } void od_rules_rule_free(od_rule_t *); void od_rules_free(od_rules_t *rules) { machine_wait_flag_set(rules->destroy_flag); pthread_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_rules_rule_free(rule); } machine_wait_flag_destroy(rules->destroy_flag); } #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, 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) { int64_t coroutine_id; coroutine_id = machine_coroutine_create( od_storage_watchdog_watch, storage->watchdog); if (coroutine_id == INVALID_COROUTINE_ID) { od_error(logger, "system", NULL, NULL, "failed to start watchdog coroutine"); return NOT_OK_RESPONSE; } } } 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_calloc(1, sizeof(*group)); if (group == NULL) { return NULL; } group->global = global; group->check_retry = 10; group->online = 0; od_list_init(&group->link); return group; } void od_rules_group_checker_run(void *arg) { od_group_checker_run_args *args = (od_group_checker_run_args *)arg; od_rule_t *group_rule = args->rule; machine_wait_flag_t *done_flag = args->done_flag; 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"); 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); machine_msg_t *msg; char *group_member = NULL; 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)); return; } while (1) { rc = machine_wait_flag_wait( done_flag, 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; } /* attach client to some route */ rc = od_attach_extended(instance, "group_checker", router, group_checker_client); if (rc != OK_RESPONSE) { continue; } od_server_t *server; server = group_checker_client->server; /* TODO: remove this loop (always works once)*/ for (int retry = 0; retry < group->check_retry; ++retry) { if (od_backend_query_send( server, "group_checker", group->group_query, NULL, strlen(group->group_query) + 1) == NOT_OK_RESPONSE) { /* Retry later. TODO: Add logging. */ break; } int response_is_read = 0; od_list_t members; od_list_init(&members); od_group_member_name_item_t *member; for (;;) { msg = od_read(&server->io, UINT32_MAX); if (msg == NULL) { if (!machine_timedout()) { od_error(&instance->logger, "group_checker", server->client, server, "read error: %s", od_io_error( &server->io)); rc = -1; break; } else { /* If timeout try read again */ continue; } } kiwi_be_type_t type; type = *(char *)machine_msg_data(msg); od_debug(&instance->logger, "group_checker", server->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)); rc = NOT_OK_RESPONSE; response_is_read = 1; break; case KIWI_BE_DATA_ROW: rc = od_group_parse_val_datarow( msg, &group_member); member = od_group_member_name_item_add( &members); member->value = group_member; break; case KIWI_BE_READY_FOR_QUERY: od_backend_ready(server, machine_msg_data(msg), machine_msg_size(msg)); response_is_read = 1; break; default: break; } machine_msg_free(msg); if (response_is_read) { break; } } if (rc == NOT_OK_RESPONSE) { od_debug(&instance->logger, "group_checker", group_checker_client, server, "group check failed"); od_list_t *it, *n; od_list_foreach_safe (&members, it, n) { member = od_container_of( it, od_group_member_name_item_t, link); if (member) { od_free(member); } } od_router_close(router, group_checker_client); break; } od_list_t *i; int count_group_users = 0; od_list_foreach (&members, i) { count_group_users++; } char **usernames = od_malloc(sizeof(char *) * count_group_users); if (usernames == NULL) { od_error(&instance->logger, "group_checker", group_checker_client, server, "out of memory"); od_router_close(router, group_checker_client); break; } int j = 0; od_list_foreach (&members, i) { od_group_member_name_item_t *member_name; member_name = od_container_of( i, od_group_member_name_item_t, link); usernames[j] = member_name->value; j++; } od_debug(&instance->logger, "group_checker", group_checker_client, server, "%d", count_group_users); for (int k = 0; k < count_group_users; k++) { od_debug(&instance->logger, "group_checker", group_checker_client, server, usernames[k]); } /* Swap usernames in router */ 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 = usernames; group_rule->users_in_group = count_group_users; 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); /* Free list */ od_list_t *it, *n; od_list_foreach_safe (&members, it, n) { member = od_container_of( it, od_group_member_name_item_t, link); if (member) { od_free(member); } } /* TODO: handle members with is_checked = 0. these rules should be inherited from the default one, if there is one */ if (rc == OK_RESPONSE) { od_debug(&instance->logger, "group_checker", group_checker_client, server, "group check success"); od_router_close(router, group_checker_client); break; } od_router_close(router, group_checker_client); /* retry */ } /* detach and unroute */ if (group_checker_client->server) { od_router_detach(router, group_checker_client); } if (group->online == 0) { od_debug(&instance->logger, "group_checker", group_checker_client, NULL, "deallocating obsolete group_checker"); break; } } 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); od_router_unlock(router); machine_wait_flag_set(group_rule->group_checker_exit_flag); od_free(args); } 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) { od_group_checker_run_args *args = od_malloc(sizeof(od_group_checker_run_args)); args->rule = rule; args->done_flag = rules->destroy_flag; rule->group_checker_exit_flag = machine_wait_flag_create(); if (rule->group_checker_exit_flag == NULL) { od_error( logger, "system", NULL, NULL, "failed to start group_checker coroutine: can't create wait flag"); return NOT_OK_RESPONSE; } rule->group_checker_machine_id = machine_coroutine_create( od_rules_group_checker_run, args); if (rule->group_checker_machine_id == INVALID_COROUTINE_ID) { 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) { od_rule_t *rule; rule = (od_rule_t *)od_malloc(sizeof(od_rule_t)); if (rule == NULL) { return NULL; } memset(rule, 0, sizeof(*rule)); rule->group_checker_machine_id = -1; rule->group_checker_exit_flag = NULL; /* pool */ rule->pool = od_rule_pool_alloc(); if (rule->pool == NULL) { od_free(rule); return NULL; } rule->user_role = OD_RULE_ROLE_UNDEF; /* backward compatibility */ rule->maintain_params = 1; rule->obsolete = 0; rule->mark = 0; rule->refs = 0; 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->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) { 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_free(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); } 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); } if (rule->group_checker_machine_id != -1) { machine_wait_flag_wait(rule->group_checker_exit_flag, UINT32_MAX); machine_wait_flag_destroy(rule->group_checker_exit_flag); } /* * 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); } od_list_unlink(&rule->link); od_free(rule); } void od_rules_ref(od_rule_t *rule) { rule->refs++; } void od_rules_unref(od_rule_t *rule) { if (rule->refs > 0) { rule->refs--; } else if (rule->refs == 0) { /* * refs can be zero in rare case of unused rule * that are obsolete by config reload * so.. do nothing here * * TODO: this is bad refs design, when no one * holds ref on new rule * and this must be refactored in future patches */ } else { /* refs < 0, of course this is terrible bug */ abort(); } if (!rule->obsolete) { return; } if (rule->refs == 0) { od_rules_rule_free(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); } 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; } static inline int od_rules_storage_compare(od_rule_storage_t *a, od_rule_storage_t *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; } 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; } /* 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; } /* maintain_params */ if (a->maintain_params != b->maintain_params) { 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; } 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) { 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++; od_hashmap_empty(rule->storage->acache); } /* 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 = od_malloc(sizeof(od_rule_key_t)); od_rule_key_init(rk); rk->usr_name = od_strndup(rule_old->user_name, rule_old->user_name_len); rk->db_name = od_strndup(rule_old->db_name, rule_old->db_name_len); od_address_range_copy(&rule_old->address_range, &rk->address_range); rk->conn_type = rule_old->conn_type; od_list_append(deleted, &rk->link); } }; /* 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 = od_malloc(sizeof(od_rule_key_t)); od_rule_key_init(rk); rk->usr_name = od_strndup(rule_new->user_name, rule_new->user_name_len); rk->db_name = od_strndup(rule_new->db_name, rule_new->db_name_len); od_address_range_copy(&rule_new->address_range, &rk->address_range); rk->conn_type = rule_new->conn_type; 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) { /* force drop rules with shared pools */ if (origin->shared_pool == NULL && rule->shared_pool == NULL && od_rules_rule_compare(origin, rule)) { origin->mark = 0; count_mark--; origin->order = rule->order; continue; /* select rules with changes what needed disconnect */ } else if (!od_rules_rule_compare_to_drop(origin, rule)) { od_rule_key_t *rk = od_malloc(sizeof(od_rule_key_t)); od_rule_key_init(rk); rk->usr_name = od_strndup(origin->user_name, origin->user_name_len); rk->db_name = od_strndup(origin->db_name, origin->db_name_len); od_address_range_copy(&origin->address_range, &rk->address_range); 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) { if (rule->group) { rule->group->online = 0; } /* * we can free rule here if refs are zero * but it will require extra synchronization * with rules gc from cron * * so let it be fried by cron eventually */ } } } /* * 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->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_copy(default_rule->storage); if (rule->storage == NULL) { return NOT_OK_RESPONSE; } 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]; 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; } 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_copy(storage); if (rule->storage == NULL) { return NOT_OK_RESPONSE; } 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", rule->db_name, rule->user_name, rule->address_range.string_value); 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_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_rules_storage_free(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); 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_log(logger, "storage", NULL, NULL, ""); } 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); } /* 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"); 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 %d", rule->pool->client_idle_timeout); od_log(logger, "rules", NULL, NULL, " pool idle_in_transaction_timeout %d", rule->pool->idle_in_transaction_timeout); 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"); } 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)); #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)); od_log(logger, "rules", NULL, NULL, " options: %s", "todo"); od_log(logger, "rules", NULL, NULL, ""); } } /* 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.5.1-rc8/sources/scram.c000066400000000000000000000645251517700303500166600ustar00rootroot00000000000000 /* * 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_SCRAM_MAX_KEY_LEN SCRAM_MAX_KEY_LEN #define OD_SCRAM_SHA_256_DEFAULT_ITERATIONS SCRAM_SHA_256_DEFAULT_ITERATIONS #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 *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; *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 iterations, const char *client_final_message, uint8_t *client_proof) { 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) { goto error; } od_scram_ctx_t *ctx = od_scram_HMAC_create(); const char *errstr = NULL; /* usage exists depending of pg version */ (void)errstr; od_scram_SaltedPassword(prepared_password, salt, SCRAM_DEFAULT_SALT_LEN, iterations, scram_state->salted_password, &errstr); uint8_t client_key[OD_SCRAM_MAX_KEY_LEN]; od_scram_ClientKey(scram_state->salted_password, client_key, &errstr); uint8_t stored_key[OD_SCRAM_MAX_KEY_LEN]; od_scram_H(client_key, OD_SCRAM_MAX_KEY_LEN, stored_key, &errstr); 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); od_free(prepared_password); return 0; error: od_free(prepared_password); return -1; } 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 iterations; int rc = read_server_first_message(scram_state, auth_data, auth_data_size, &server_nonce, &server_nonce_size, &salt, &iterations); if (rc == -1) { return NULL; } #define SCRAM_FINAL_MAX_SIZE 512 char result[SCRAM_FINAL_MAX_SIZE]; char attributes[] = "c=biws,r="; 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, 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]; 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 < 0) { 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_key[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++) { client_key[i] = client_proof[i] ^ client_signature[i]; } od_scram_H(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.5.1-rc8/sources/server.c000066400000000000000000000070161517700303500170510ustar00rootroot00000000000000/* * 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->tls != NULL) { machine_tls_free(server->tls); server->tls = NULL; } if (server->prep_stmts) { od_server_pstmt_hashmap_free(server->prep_stmts); } 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) { assert(server->client == NULL); assert(client->server == NULL); 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; assert(client != NULL); assert(server->state == OD_SERVER_ACTIVE); assert(server == client->server); server->client_pinned = 0; client->server = NULL; server->client = NULL; 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; } } odyssey-1.5.1-rc8/sources/setproctitle.c000066400000000000000000000010041517700303500202530ustar00rootroot00000000000000#include #include #include #include od_retcode_t od_setproctitlef(char **argv_ptr, int argv_len, char *fmt, ...) { char title[OD_MAX_PROC_TITLE_LEN]; va_list args; va_start(args, fmt); size_t title_len = od_vsnprintf(title, sizeof(title), fmt, args); va_end(args); /* dirty hack */ title[title_len] = '\0'; /* clean up previous string */ memset(*argv_ptr, 0, argv_len); memcpy(*argv_ptr, title, title_len + 1); return OK_RESPONSE; } odyssey-1.5.1-rc8/sources/shared_pool.c000066400000000000000000000023751517700303500200450ustar00rootroot00000000000000/* * 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); pthread_spin_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); pthread_spin_init(&sp->lock, PTHREAD_PROCESS_PRIVATE); return sp; } od_shared_pool_t *od_shared_pool_ref(od_shared_pool_t *sp) { pthread_spin_lock(&sp->lock); ++sp->refs; pthread_spin_unlock(&sp->lock); return sp; } void od_shared_pool_unref(od_shared_pool_t *sp) { int refs; pthread_spin_lock(&sp->lock); refs = sp->refs--; pthread_spin_unlock(&sp->lock); if (refs == 1) { od_shared_pool_free(sp); } } odyssey-1.5.1-rc8/sources/sighandler.c000066400000000000000000000210561517700303500176630ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline od_retcode_t od_system_gracefully_killer_invoke(od_system_t *system, machine_channel_t *channel) { od_instance_t *instance = system->global->instance; int64_t shut_worker_id = od_instance_get_shutdown_worker_id(instance); if (shut_worker_id != INVALID_COROUTINE_ID) { return OK_RESPONSE; } /* freed in od_grac_shutdown_worker */ od_grac_shutdown_worker_arg_t *arg = od_malloc(sizeof(od_grac_shutdown_worker_arg_t)); if (arg == NULL) { od_error(&instance->logger, "gracefully_killer", NULL, NULL, "failed to allocate grac_shutdown_worker_arg"); return NOT_OK_RESPONSE; } arg->system = system; arg->channel = channel; int64_t mid; mid = machine_create("shutdowner", od_grac_shutdown_worker, arg); if (mid == -1) { od_error(&instance->logger, "gracefully_killer", NULL, NULL, "failed to invoke gracefully killer coroutine"); od_free(arg); return NOT_OK_RESPONSE; } od_instance_set_shutdown_worker_id(instance, mid); return OK_RESPONSE; } 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_shutdown(od_system_t *system, od_instance_t *instance) { od_log(&instance->logger, "system", NULL, NULL, "SIGINT received, shutting down"); /* lock here */ od_cron_stop(system->global->cron); /* Prevent OpenSSL usage during deinitialization */ od_worker_pool_wait(); od_extension_free(&instance->logger, system->global->extensions); #ifdef OD_SYSTEM_SHUTDOWN_CLEANUP od_router_free(system->global->router); od_system_cleanup(system); /* stop machinaruim and free */ od_instance_free(instance); #endif } 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 graceful_shutdown_finished = false; while (!graceful_shutdown_finished) { 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"); return; } int type = machine_msg_type(msg); switch (type) { case OD_MSG_SIGNAL_RECEIVED: break; case OD_MSG_GRAC_SHUTDOWN_FINISHED: graceful_shutdown_finished = true; machine_msg_free(msg); continue; default: assert(0); }; int sig = *(int *)machine_msg_data(msg); machine_msg_free(msg); switch (sig) { case SIGTERM: case SIGINT: 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(); } od_system_gracefully_killer_invoke(system, channel); 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"); od_system_config_reload(system); 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"); rc = od_logger_reopen( &instance->logger, instance->config.log_file); if (rc == -1) { od_error( &instance->logger, "system", NULL, NULL, "failed to reopen log file '%s'", instance->config.log_file); } else { od_log(&instance->logger, "system", NULL, NULL, "log reopened"); } } 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; } } od_soft_oom_stop_checker(&od_global_get()->soft_oom); machine_wait(od_instance_get_shutdown_worker_id(instance)); machine_cancel(sigwaiter_id); machine_join(sigwaiter_id); machine_channel_free(channel); } odyssey-1.5.1-rc8/sources/soft_oom.c000066400000000000000000000241711517700303500173710ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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: %ld kB", &mem_available) == 1) { continue; } if (sscanf(line, "MemTotal: %ld 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 = %ld, available = %ld", 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): %lu 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 (%lu) >= limit (%lu), 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; } if (rc == -1 && machine_errno() != ETIMEDOUT) { od_gerror(SOFT_OOM_LOG_CONTEXT, NULL, NULL, "stop flag wait failed: %s (%d)", strerror(machine_errno()), machine_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.5.1-rc8/sources/storage.c000066400000000000000000000347571517700303500172230ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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; pthread_spin_init(&status->values_lock, PTHREAD_PROCESS_PRIVATE); } void od_storage_endpoint_status_destroy(od_storage_endpoint_status_t *status) { pthread_spin_destroy(&status->values_lock); } bool od_storage_endpoint_status_is_outdated( od_storage_endpoint_status_t *status, uint64_t recheck_interval) { pthread_spin_lock(&status->values_lock); uint64_t last_update_time_ms = status->last_update_time_ms; pthread_spin_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) { pthread_spin_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; pthread_spin_unlock(&status->values_lock); } void od_storage_endpoint_status_set(od_storage_endpoint_status_t *status, const od_storage_endpoint_status_t *value) { pthread_spin_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; pthread_spin_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(); 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 = 1; watchdog->is_finished = machine_wait_flag_create(); if (watchdog->is_finished == NULL) { od_free(watchdog); return NULL; } pthread_mutex_init(&watchdog->mu, NULL); return watchdog; } static inline int od_storage_watchdog_is_online(od_storage_watchdog_t *watchdog) { int ret; pthread_mutex_lock(&watchdog->mu); ret = watchdog->online; pthread_mutex_unlock(&watchdog->mu); return ret; } static inline int od_storage_watchdog_set_offline(od_storage_watchdog_t *watchdog) { pthread_mutex_lock(&watchdog->mu); watchdog->online = 0; pthread_mutex_unlock(&watchdog->mu); return OK_RESPONSE; } static inline void od_storage_watchdog_soft_exit(od_storage_watchdog_t *watchdog) { od_storage_watchdog_set_offline(watchdog); machine_wait_flag_wait(watchdog->is_finished, UINT32_MAX); od_storage_watchdog_free(watchdog); } int od_storage_watchdog_free(od_storage_watchdog_t *watchdog) { if (watchdog == NULL) { return NOT_OK_RESPONSE; } if (watchdog->query) { od_free(watchdog->query); } machine_wait_flag_destroy(watchdog->is_finished); pthread_mutex_destroy(&watchdog->mu); od_free(watchdog); return OK_RESPONSE; } od_storage_endpoint_t * od_rules_storage_next_endpoint(od_rule_storage_t *storage) { assert(storage->endpoints_count >= 1); if (storage->endpoints_count == 1) { return &storage->endpoints[0]; } for (;;) { size_t curr = atomic_load(&storage->rr_counter); atomic_size_t next = curr + 1 >= storage->endpoints_count ? 0 : curr + 1; if (atomic_compare_exchange_strong(&storage->rr_counter, &curr, next)) { return &storage->endpoints[next]; } } } od_storage_endpoint_t * od_rules_storage_localhost_or_next_endpoint(od_rule_storage_t *storage) { /* TODO: do not iterate over endpoints in searching of localhost ? */ for (size_t i = 0; i < storage->endpoints_count; ++i) { od_storage_endpoint_t *endpoint = &storage->endpoints[i]; if (od_address_is_localhost(&endpoint->address)) { return endpoint; } } return od_rules_storage_next_endpoint(storage); } 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->tls_opts = od_tls_opts_alloc(); if (storage->tls_opts == NULL) { od_free(storage); return NULL; } storage->endpoints_status_poll_interval_ms = 1000; atomic_init(&storage->rr_counter, 0); #define OD_STORAGE_DEFAULT_HASHMAP_SZ 420u storage->acache = od_auth_query_create_cache(OD_STORAGE_DEFAULT_HASHMAP_SZ); od_list_init(&storage->link); return storage; } void od_rules_storage_free(od_rule_storage_t *storage) { if (storage->watchdog) { od_storage_watchdog_soft_exit(storage->watchdog); } 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); } if (storage->acache) { od_hashmap_free(storage->acache); } od_list_unlink(&storage->link); od_free(storage); } od_rule_storage_t *od_rules_storage_copy(od_rule_storage_t *storage) { od_rule_storage_t *copy; copy = od_rules_storage_allocate(); if (copy == NULL) { return NULL; } copy->storage_type = storage->storage_type; copy->name = od_strdup(storage->name); copy->server_max_routing = storage->server_max_routing; if (copy->name == NULL) { goto error; } copy->type = od_strdup(storage->type); if (copy->type == NULL) { goto error; } if (storage->host) { copy->host = od_strdup(storage->host); if (copy->host == NULL) { goto error; } } copy->port = storage->port; copy->tls_opts->tls_mode = storage->tls_opts->tls_mode; if (storage->tls_opts->tls) { copy->tls_opts->tls = od_strdup(storage->tls_opts->tls); if (copy->tls_opts->tls == NULL) { goto error; } } if (storage->tls_opts->tls_ca_file) { copy->tls_opts->tls_ca_file = od_strdup(storage->tls_opts->tls_ca_file); if (copy->tls_opts->tls_ca_file == NULL) { goto error; } } if (storage->tls_opts->tls_key_file) { copy->tls_opts->tls_key_file = od_strdup(storage->tls_opts->tls_key_file); if (copy->tls_opts->tls_key_file == NULL) { goto error; } } if (storage->tls_opts->tls_cert_file) { copy->tls_opts->tls_cert_file = od_strdup(storage->tls_opts->tls_cert_file); if (copy->tls_opts->tls_cert_file == NULL) { goto error; } } if (storage->tls_opts->tls_protocols) { copy->tls_opts->tls_protocols = od_strdup(storage->tls_opts->tls_protocols); if (copy->tls_opts->tls_protocols == NULL) { goto error; } } if (storage->endpoints_count) { copy->endpoints_count = storage->endpoints_count; copy->endpoints = od_malloc(sizeof(od_storage_endpoint_t) * copy->endpoints_count); if (copy->endpoints == NULL) { goto error; } for (size_t i = 0; i < copy->endpoints_count; ++i) { od_address_init(©->endpoints[i].address); if (od_address_copy(©->endpoints[i].address, &storage->endpoints[i].address) != OK_RESPONSE) { goto error; } od_storage_endpoint_status_init( ©->endpoints[i].status); } } copy->endpoints_status_poll_interval_ms = storage->endpoints_status_poll_interval_ms; /* storage auth cache not copied */ return copy; error: od_rules_storage_free(copy); return NULL; } static inline int strtol_safe(const char *s, int len) { const int buff_len = 32; char buff[buff_len]; memset(buff, 0, sizeof(buff)); memcpy(buff, s, len); return strtol(buff, NULL, 0); } static inline int od_storage_watchdog_parse_lag_from_datarow(machine_msg_t *msg, int *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; } *repl_lag = strtol_safe(pos, (int)lag_len); return OK_RESPONSE; error: return NOT_OK_RESPONSE; } static inline int od_router_update_heartbeat_cb(od_route_t *route, void **argv) { od_route_lock(route); route->last_heartbeat = *(int *)argv[0]; od_route_unlock(route); return 0; } 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_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_storage_watchdog_prepare_client(watchdog); if (watchdog_client == NULL) { od_error(&instance->logger, "watchdog", NULL, NULL, "route storage watchdog failed to prepare client"); return NULL; } int rc; rc = od_attach_extended(instance, "watchdog", router, watchdog_client); if (rc != OK_RESPONSE) { od_router_unroute(router, watchdog_client); od_client_free_extended(watchdog_client); return NULL; } return watchdog_client; } static inline void od_storage_update_route_last_heartbeats( od_instance_t *instance, od_client_t *watchdog_client, od_server_t *server, od_router_t *router, int last_heartbeat) { od_log(&instance->logger, "watchdog", watchdog_client, server, "send heartbeat arenda update to routes with value %d", last_heartbeat); void *argv[] = { &last_heartbeat }; od_router_foreach(router, od_router_update_heartbeat_cb, argv); } static inline void od_storage_watchdog_do_polling_step(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_storage_create_and_connect_watchdog_client(watchdog); if (watchdog_client == NULL) { return; } od_server_t *server; server = watchdog_client->server; machine_msg_t *msg; msg = od_query_do(server, "watchdog", watchdog->query, NULL); if (msg == NULL) { od_error(&instance->logger, "watchdog", watchdog_client, server, "receive msg failed, closing backend connection"); od_storage_watchdog_close_client(watchdog, watchdog_client); return; } int last_heartbeat; if (od_storage_watchdog_parse_lag_from_datarow(msg, &last_heartbeat) == 0) { od_storage_update_route_last_heartbeats(instance, watchdog_client, server, router, last_heartbeat); } else { od_error(&instance->logger, "watchdog", watchdog_client, server, "can't parse lag"); } machine_msg_free(msg); od_storage_watchdog_close_client(watchdog, watchdog_client); } static inline void od_storage_watchdog_do_polling_loop(od_storage_watchdog_t *watchdog) { while (od_storage_watchdog_is_online(watchdog)) { od_storage_watchdog_do_polling_step(watchdog); machine_sleep(1000); } } 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); machine_wait_flag_set(watchdog->is_finished); } 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.5.1-rc8/sources/stream.c000066400000000000000000000527661517700303500170520ustar00rootroot00000000000000/* * 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, "got CommandCompelete message with tag LISTEN, client is pinned to server"); server->client_pinned = 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: 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) { 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_header(data, sizeof(kiwi_header_t), &body); if (rc != 0) { od_gerror(ctx, server->client, server, "invalid package header from server"); 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); if (rc != 0) { status = 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_header(data, sizeof(kiwi_header_t), &body); if (rc != 0) { od_gerror(ctx, client, client->server, "invalid package header from client"); 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); 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 */ assert(processed == 0); break; } if (status != OD_OK) { 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; } } 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); 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); /* * 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); 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; } } 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; } 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.5.1-rc8/sources/system.c000066400000000000000000000433011517700303500170640ustar00rootroot00000000000000 /* * 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 static inline od_retcode_t od_system_server_pre_stop(od_system_server_t *server) { /* shutdown */ od_retcode_t rc; rc = mm_io_shutdown_receptions(server->io); if (rc == -1) { return NOT_OK_RESPONSE; } return OK_RESPONSE; } static inline void od_system_server(void *arg) { od_system_server_t *server = arg; od_instance_t *instance = server->global->instance; od_router_t *router = server->global->router; /* * 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(); for (;;) { /* do not accept new client */ if (atomic_load(&server->closed)) { od_dbg_printf_on_dvl_lvl(1, "%s shutting receptions\n", server->sid.id); od_system_server_pre_stop(server); server->pre_exited = true; break; } /* 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, 1000 /* 1 sec */); if (rc == -1) { int errno_ = machine_errno(); if (errno_ == ETIMEDOUT) { /* just no new connections for last accept interval */ continue; } od_error(&instance->logger, "server", NULL, NULL, "accept failed: %s", mm_io_error(server->io)); if (errno_ == EADDRINUSE) { break; } 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"); rc = od_io_prepare(&client->io, client_io); if (rc == -1) { od_error( &instance->logger, "server", NULL, NULL, "failed to allocate client io object, errno = %d (%s)", machine_errno(), strerror(machine_errno())); mm_io_close(client_io); mm_io_free(client_io); od_client_free(client); continue; } client->rule = NULL; client->config_listen = server->config; 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 *)); machine_msg_set_type(msg, OD_MSG_CLIENT_NEW); memcpy(machine_msg_data(msg), &client, sizeof(od_client_t *)); od_worker_pool_t *worker_pool = server->global->worker_pool; od_atomic_u32_inc(&router->clients_routing); od_worker_pool_feed(worker_pool, msg); bool warning_emitted = false; while (od_atomic_u32_of(&router->clients_routing) >= (uint32_t)instance->config.client_max_routing) { if (!warning_emitted) { /* TODO: AB: Use WARNING here, it's not an error */ od_error(&instance->logger, "client_max_routing", client, NULL, "client is waiting in routing queue"); warning_emitted = true; } machine_sleep(1); } } mm_io_shutdown(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); } } 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)); server->io = NULL; server->tls = NULL; od_id_generate(&server->sid, "sid"); atomic_init(&server->closed, false); server->pre_exited = false; server->coro_id = -1; return server; } void od_system_server_free(od_system_server_t *server) { if (server->tls) { /* Free tls */ machine_tls_free(server->tls); } 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, struct addrinfo *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, %d) failed", saddr_un.sun_path, instance->config.unix_socket_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); od_dbg_printf_on_dvl_lvl(1, "server %s started successfully on %s\n", server->sid.id, addr_name); 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); struct addrinfo *ai = NULL; rc = machine_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++; } freeaddrinfo(ai); continue; } struct addrinfo *orig = ai; while (ai) { rc = od_system_server_start(system, listen, ai); if (rc == 0) { binded++; } ai = ai->ai_next; } freeaddrinfo(orig); } #ifdef ODYSSEY_VERSION_GIT od_setproctitlef( &instance->orig_argv_ptr, instance->orig_argv_ptr_len, "odyssey %s (git %s) listening and accepting new connections ", ODYSSEY_VERSION_NUMBER, ODYSSEY_VERSION_GIT); #else od_setproctitlef(&instance->orig_argv_ptr, instance->orig_argv_ptr_len, "odyssey %s listening and accepting new connections ", 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); } static inline void od_move_storages(od_router_t *router, od_rules_t *rules) { od_list_t *i, *n; pthread_mutex_lock(&router->rules.mu); pthread_mutex_lock(&rules->mu); 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_add(&router->rules, storage); } pthread_mutex_unlock(&rules->mu); pthread_mutex_unlock(&router->rules.mu); } void od_system_config_reload(od_system_t *system) { od_instance_t *instance = system->global->instance; od_router_t *router = system->global->router; od_extension_t *extensions = system->global->extensions; 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); pthread_mutex_lock(&router->rules.mu); od_rules_cleanup(&router->rules); od_error_t error; od_error_init(&error); 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_config_reader_import(&config, &rules, &error, extensions, system->global, &hba_rules, instance->config_file); if (rc == -1) { od_error(&instance->logger, "config", NULL, NULL, "%s", error.error); pthread_mutex_unlock(&router->rules.mu); od_config_free(&config); od_rules_free(&rules); return; } rc = od_config_validate(&config, &instance->logger); if (rc == -1) { pthread_mutex_unlock(&router->rules.mu); od_config_free(&config); od_rules_free(&rules); return; } rc = od_rules_validate(&rules, &config, &instance->logger); if (rc == -1) { pthread_mutex_unlock(&router->rules.mu); od_config_free(&config); od_rules_free(&rules); return; } od_config_reload(&instance->config, &config); 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) { pthread_mutex_unlock(&router->rules.mu); od_config_free(&config); od_rules_free(&rules); return; } pthread_mutex_unlock(&router->rules.mu); /* 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; } if (listen_config == NULL) { od_log(&instance->logger, "reload-config", NULL, NULL, "failed to match listen config for %s:%d", server->config->host == NULL ? "(NULL)" : server->config->host, 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", server->config->host == NULL ? "(NULL)" : server->config->host, server->config->port); server->config->tls_opts->tls_mode = listen_config->tls_opts->tls_mode; } if (server->config->tls_opts->tls_mode != OD_CONFIG_TLS_DISABLE) { machine_tls_t *tls = od_tls_frontend(server->config); /* TODO: support changing cert files */ if (tls != NULL) { server->tls = tls; } } } 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; updates = od_router_reconfigure(router, &rules); od_log(&instance->logger, "rules", NULL, NULL, "dispatching storage watchdogs"); od_rules_storages_watchdogs_run(&instance->logger, &rules); od_move_storages(router, &rules); /* free unused rules */ od_rules_free(&rules); od_log(&instance->logger, "rules", NULL, NULL, "%d routes created/deleted and scheduled for removal", updates); od_rules_groups_checkers_run(&instance->logger, &router->rules); } static inline void od_system(void *arg) { od_system_t *system = arg; od_instance_t *instance = system->global->instance; od_router_t *router = system->global->router; instance->pstmts = od_global_pstmts_map_create(); 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; } /* start cron coroutine */ od_cron_t *cron = system->global->cron; int rc; rc = od_cron_start(cron, system->global); if (rc == -1) { return; } /* 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_error(&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_error(&instance->logger, "system", NULL, NULL, "failed to bind any listen address"); exit(1); } od_rules_storages_watchdogs_run(&instance->logger, &router->rules); od_rules_groups_checkers_run(&instance->logger, &router->rules); machine_wait_nb(system->sighandler_machine); 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); machine_join(server->coro_id); } od_router_free(router); od_logger_shutdown(&instance->logger); od_logger_wait_finish(&instance->logger); od_instance_free(instance); od_global_destroy(od_global_get()); od_system_free(system); } void od_system_init(od_system_t *system) { system->machine = -1; system->sighandler_machine = -1; 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.5.1-rc8/sources/systemd_notify.c000066400000000000000000000013351517700303500206210ustar00rootroot00000000000000/* * 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.5.1-rc8/sources/systemd_notify.h000066400000000000000000000011451517700303500206250ustar00rootroot00000000000000#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.5.1-rc8/sources/tdigest.c000066400000000000000000000205221517700303500172030ustar00rootroot00000000000000 /* * 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) { 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.5.1-rc8/sources/tests/000077500000000000000000000000001517700303500165355ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/tests/README.md000066400000000000000000000003521517700303500200140ustar00rootroot00000000000000### 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.5.1-rc8/sources/tests/kiwi/000077500000000000000000000000001517700303500175005ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/tests/kiwi/test_kiwi_enquote.c000066400000000000000000000020151517700303500234040ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/kiwi/test_kiwi_pgoptions.c000066400000000000000000000125371517700303500237600ustar00rootroot00000000000000#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", -1 /* 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 */); } odyssey-1.5.1-rc8/sources/tests/machinarium/000077500000000000000000000000001517700303500210325ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/tests/machinarium/ca.crt000066400000000000000000000037101517700303500221300ustar00rootroot00000000000000-----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.5.1-rc8/sources/tests/machinarium/client.crt000066400000000000000000000035121517700303500230230ustar00rootroot00000000000000-----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.5.1-rc8/sources/tests/machinarium/client.key000066400000000000000000000063101517700303500230220ustar00rootroot00000000000000-----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.5.1-rc8/sources/tests/machinarium/generate-test-certs.sh000077500000000000000000000012551517700303500252610ustar00rootroot00000000000000#!/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.5.1-rc8/sources/tests/machinarium/server.crt000066400000000000000000000035121517700303500230530ustar00rootroot00000000000000-----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.5.1-rc8/sources/tests/machinarium/server.key000066400000000000000000000063101517700303500230520ustar00rootroot00000000000000-----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.5.1-rc8/sources/tests/machinarium/test_accept_cancel.c000066400000000000000000000021421517700303500250000ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_accept_timeout.c000066400000000000000000000015311517700303500252420ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_advice_keepalive_usr_timeout.c000066400000000000000000000004371517700303500301600ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_channel_cancel.c000066400000000000000000000014401517700303500251510ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_create.c000066400000000000000000000007241517700303500251730ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_rw0.c000066400000000000000000000013541517700303500244400ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_rw1.c000066400000000000000000000022031517700303500244330ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_rw2.c000066400000000000000000000022361517700303500244420ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_rw3.c000066400000000000000000000035351517700303500244460ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_rw4.c000066400000000000000000000035341517700303500244460ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_shared_create.c000066400000000000000000000007331517700303500265210ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_shared_rw0.c000066400000000000000000000013661517700303500257710ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_shared_rw1.c000066400000000000000000000022221517700303500257620ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_shared_rw2.c000066400000000000000000000022701517700303500257660ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_channel_timeout.c000066400000000000000000000010531517700303500254120ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_client_server0.c000066400000000000000000000036501517700303500251650ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_client_server1.c000066400000000000000000000053471517700303500251730ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_client_server2.c000066400000000000000000000045531517700303500251720ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_client_server_unix_socket.c000066400000000000000000000040451517700303500275170ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_client_server_unix_socket_no_msg.c000066400000000000000000000032471517700303500310640ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_condition0.c000066400000000000000000000116571517700303500243150ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_config.c000066400000000000000000000003541517700303500235040ustar00rootroot00000000000000 #include #include #include void machinarium_test_config(void) { machinarium_set_pool_size(1); machinarium_init(); test(machinarium_init() == -1); machinarium_free(); } odyssey-1.5.1-rc8/sources/tests/machinarium/test_connect.c000066400000000000000000000020251517700303500236650ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_connect_cancel0.c000066400000000000000000000017161517700303500252600ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_connect_cancel1.c000066400000000000000000000017511517700303500252600ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_connect_timeout.c000066400000000000000000000016021517700303500254330ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_context_switch.c000066400000000000000000000011701517700303500253010ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_coroutine_names.c000066400000000000000000000045731517700303500254400ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_create0.c000066400000000000000000000006621517700303500235640ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_create1.c000066400000000000000000000014101517700303500235550ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_getaddrinfo0.c000066400000000000000000000011001517700303500245730ustar00rootroot00000000000000 #include #include static void test_gai(void *arg) { (void)arg; struct addrinfo *res = NULL; int rc = machine_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { 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.5.1-rc8/sources/tests/machinarium/test_getaddrinfo1.c000066400000000000000000000020461517700303500246060ustar00rootroot00000000000000 #include #include static void test_gai0(void *arg) { (void)arg; struct addrinfo *res = NULL; int rc = machine_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { freeaddrinfo(res); } } } static void test_gai1(void *arg) { (void)arg; struct addrinfo *res = NULL; int rc = machine_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { 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.5.1-rc8/sources/tests/machinarium/test_getaddrinfo2.c000066400000000000000000000016461517700303500246140ustar00rootroot00000000000000 #include #include static int gai_complete = 0; static void test_gai_coroutine(void *arg) { (void)arg; struct addrinfo *res = NULL; int rc = machine_getaddrinfo("localhost", "http", NULL, &res, UINT32_MAX); if (rc < 0) { printf("failed to resolve address\n"); } else { test(res != NULL); if (res) { 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.5.1-rc8/sources/tests/machinarium/test_init.c000066400000000000000000000002261517700303500232000ustar00rootroot00000000000000 #include #include void machinarium_test_init(void) { machinarium_init(); machinarium_free(); } odyssey-1.5.1-rc8/sources/tests/machinarium/test_io_new.c000066400000000000000000000006501517700303500235160ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_join.c000066400000000000000000000013501517700303500231730ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_mutex_coroutines.c000066400000000000000000000031701517700303500256520ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_mutex_threads.c000066400000000000000000000032111517700303500251060ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_mutex_timeout.c000066400000000000000000000020711517700303500251450ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_producer_consumer0.c000066400000000000000000000020501517700303500260500ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_producer_consumer1.c000066400000000000000000000033071517700303500260570ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_producer_consumer2.c000066400000000000000000000022321517700303500260540ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_queue.c000066400000000000000000000111551517700303500233640ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_read_10mb0.c000066400000000000000000000042661517700303500240570ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_read_10mb1.c000066400000000000000000000041751517700303500240570ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_read_10mb2.c000066400000000000000000000040111517700303500240450ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_read_cancel.c000066400000000000000000000033011517700303500244520ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_read_timeout.c000066400000000000000000000031101517700303500247110ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_read_var.c000066400000000000000000000051101517700303500240150ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_ring_buffer.c000066400000000000000000000077461517700303500245430ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_signal0.c000066400000000000000000000014361517700303500235760ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_signal1.c000066400000000000000000000015201517700303500235710ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_sleep.c000066400000000000000000000015341517700303500233500ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_sleep_cancel0.c000066400000000000000000000013051517700303500247310ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_sleep_yield.c000066400000000000000000000005711517700303500245360ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_sleeplock.c000066400000000000000000000017241517700303500242220ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_stat.c000066400000000000000000000034521517700303500232140ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls0.c000066400000000000000000000056341517700303500231270ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_read_10mb0.c000066400000000000000000000061631517700303500247370ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_read_10mb1.c000066400000000000000000000061411517700303500247340ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_read_10mb2.c000066400000000000000000000057551517700303500247470ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_read_multithread.c000066400000000000000000000063611517700303500264420ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_read_var.c000066400000000000000000000070541517700303500247100ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_unix_socket.c000066400000000000000000000060301517700303500254510ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tls_unix_socket_no_msg.c000066400000000000000000000052321517700303500270160ustar00rootroot00000000000000 #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.5.1-rc8/sources/tests/machinarium/test_tsan_simple_race_example.c000066400000000000000000000027741517700303500272720ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_vrb.c000066400000000000000000000260411517700303500230310ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_flag_simple.c000066400000000000000000000100131517700303500255360ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_flag_timeout.c000066400000000000000000000011651517700303500257430ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_group_lifetime.c000066400000000000000000000102111517700303500262660ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_group_simple.c000066400000000000000000000053061517700303500257720ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_group_timeout.c000066400000000000000000000017751517700303500261750ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_compare_wait_timeout.c000066400000000000000000000024151517700303500305360ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_compare_wait_wrong_value.c000066400000000000000000000037141517700303500314030ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_notify_after_compare_wait.c000066400000000000000000000053661517700303500315510ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_notify_after_wait.c000066400000000000000000000024421517700303500300330ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_notify_all.c000066400000000000000000000040731517700303500264600ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_one_producer_multiple_consumers.c000066400000000000000000000035641517700303500330210ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_one_producer_multiple_consumers_threads.c000066400000000000000000000040121517700303500345200ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/machinarium/test_wait_list_without_notify.c000066400000000000000000000022551517700303500274130ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/odyssey-test.conf000066400000000000000000000025141517700303500220620ustar00rootroot00000000000000daemonize 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.5.1-rc8/sources/tests/odyssey/000077500000000000000000000000001517700303500202345ustar00rootroot00000000000000odyssey-1.5.1-rc8/sources/tests/odyssey/test_address.c000066400000000000000000000160601517700303500230670ustar00rootroot00000000000000#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)); } 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.5.1-rc8/sources/tests/odyssey/test_affinity.c000066400000000000000000000341751517700303500232620ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/odyssey/test_attribute.c000066400000000000000000000041461517700303500234470ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/odyssey/test_hashmap.c000066400000000000000000000103121517700303500230550ustar00rootroot00000000000000#include #include #include #include #define OD_TEST_DEFAULT_HASHMAP_SZ 420u #define OD_TEST_LARGE_HASHMAP_SZ 65534u /* Generate len-size string and safe to buf */ void generate_random_string(char *buf, size_t len) { static const char ALPHABET[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./,\\?,<>"; static const int LEN_ALPHABET = sizeof(ALPHABET) - 1; --len; for (size_t i = 0; i < len; i++) { buf[i] = ALPHABET[rand() % LEN_ALPHABET]; } buf[len] = '\0'; } /* Save data in od_hashmap_elt_t, calc keyhash if need */ static inline void test_hashmap_init_item(od_hashmap_elt_t *item, void *data, size_t len, od_hash_t *keyhash) { item->data = data; item->len = len; if (keyhash != NULL) { *keyhash = od_murmur_hash(item->data, item->len); } } /* Add element to hashmap by key */ void test_hashmap_add_element(od_hashmap_t *hm, void *key_data, size_t key_len, void *value_data, size_t value_len) { od_hashmap_elt_t key; od_hash_t keyhash; od_hashmap_elt_t *value; test_hashmap_init_item(&key, key_data, key_len, &keyhash); value = od_hashmap_lock_key(hm, keyhash, &key); test_hashmap_init_item(value, value_data, value_len, NULL); int rc; rc = od_hashmap_unlock_key(hm, keyhash, &key); test(rc == 0); } /* Compare expected value and value by key */ void test_hashmap_compare_element_by_key(od_hashmap_t *hm, void *key_data, size_t key_len, void *value_data, size_t value_len) { od_hash_t keyhash; od_hashmap_elt_t key; od_hashmap_elt_t *ptr; test_hashmap_init_item(&key, key_data, key_len, &keyhash); ptr = od_hashmap_find(hm, keyhash, &key); test(ptr != NULL); test(ptr->len == value_len); test(memcmp(value_data, ptr->data, ptr->len) == 0); } /* Small test to one item */ void test_hashmap_one_item(void) { od_hashmap_t *hm; hm = od_hashmap_create(OD_TEST_DEFAULT_HASHMAP_SZ); char key_data[] = "sqrt"; char *value_data = malloc(12); strcpy(value_data, "192.168.1.1"); test_hashmap_add_element(hm, key_data, 5, value_data, 12); test_hashmap_compare_element_by_key(hm, key_data, 5, value_data, 12); od_hashmap_free(hm); } /* Initializing a hashmap of hm_size size and add hm_size elements of elemet_size sizes */ void test_hashmap_many_items(const size_t hm_size, const size_t element_size) { od_hashmap_t *hm; hm = od_hashmap_create(hm_size); char **strings = malloc(hm_size * sizeof(char *)); size_t *keys = malloc(hm_size * sizeof(size_t)); for (size_t i = 0; i < hm_size; ++i) { strings[i] = (char *)malloc(element_size); generate_random_string(strings[i], element_size); keys[i] = i; test_hashmap_add_element(hm, &keys[i], sizeof(i), strings[i], element_size); } for (size_t i = 0; i < hm_size; ++i) { test_hashmap_compare_element_by_key(hm, &keys[i], sizeof(i), strings[i], element_size); } od_hashmap_free(hm); free(keys); free(strings); } /* Test insert method of hashmap */ void test_hashmap_insert(void) { od_hashmap_t *hm; hm = od_hashmap_create(OD_TEST_DEFAULT_HASHMAP_SZ); od_hash_t keyhash; od_hashmap_elt_t key; od_hashmap_elt_t value_1, value_2; od_hashmap_elt_t *value_ptr_1 = &value_1, *value_ptr_2 = &value_2; int rc; const size_t LEN = 40; char key_data[] = "sqrt"; char *value_data_1 = malloc(LEN); generate_random_string(value_data_1, LEN); char *value_data_2 = malloc(LEN); generate_random_string(value_data_2, LEN); test_hashmap_init_item(&key, key_data, 5, &keyhash); test_hashmap_init_item(value_ptr_1, value_data_1, LEN, NULL); test_hashmap_init_item(value_ptr_2, value_data_2, LEN, NULL); rc = od_hashmap_insert(hm, keyhash, &key, &value_ptr_1); test(rc == 0); rc = od_hashmap_insert(hm, keyhash, &key, &value_ptr_2); test(rc == 1); od_hashmap_free(hm); free(value_data_1); free(value_data_2); } static void tester(void *a) { (void)a; srand(time(NULL)); test_hashmap_one_item(); test_hashmap_many_items(OD_TEST_DEFAULT_HASHMAP_SZ, 40); test_hashmap_many_items(OD_TEST_LARGE_HASHMAP_SZ, 100); test_hashmap_insert(); } void odyssey_test_hashmap(void) { machinarium_init(); int64_t rc; rc = machine_create("tester", tester, NULL); test(rc > 0); test(machine_wait(rc) == 0); machinarium_free(); } odyssey-1.5.1-rc8/sources/tests/odyssey/test_hba_parse.c000066400000000000000000000022401517700303500233610ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/odyssey/test_pstmt.c000066400000000000000000000113001517700303500226010ustar00rootroot00000000000000#include #include #include #include #include #include void test_pstmt_client_hashmap(void) { od_client_t *client = od_client_allocate(); client->prep_stmt_ids = od_client_pstmt_hashmap_create(); test(client->prep_stmt_ids != NULL); od_pstmt_t pstmt; memset(&pstmt, 0, sizeof(pstmt)); od_pstmt_t pstmt2; memset(&pstmt2, 0, sizeof(pstmt2)); /* works correctly with unnamed */ test(od_client_has_pstmt(client, "") == 0); test(od_client_get_pstmt(client, "") == NULL); test(od_client_add_pstmt(client, "", &pstmt) == 0); test(od_client_has_pstmt(client, "") == 1); test(od_client_get_pstmt(client, "") == &pstmt); /* allows to redefine unnamed statement */ test(od_client_add_pstmt(client, "", &pstmt2) == 0); test(od_client_has_pstmt(client, "") == 1); test(od_client_get_pstmt(client, "") == &pstmt2); test(od_client_remove_pstmt(client, "") == 0); test(od_client_has_pstmt(client, "") == 0); test(od_client_get_pstmt(client, "") == NULL); 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", &pstmt) == 0); test(od_client_has_pstmt(client, "P_0") == 1); test(od_client_get_pstmt(client, "P_0") == &pstmt); /* do not add twice */ test(od_client_add_pstmt(client, "P_0", &pstmt) == 1); test(od_client_has_pstmt(client, "P_0") == 1); test(od_client_get_pstmt(client, "P_0") == &pstmt); 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", &pstmt2) == 0); test(od_client_has_pstmt(client, "P_1") == 1); test(od_client_get_pstmt(client, "P_1") == &pstmt2); 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") == &pstmt2); test(od_client_has_pstmt(client, "P_0") == 0); test(od_client_get_pstmt(client, "P_0") == NULL); 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", &pstmt) == 0); test(od_client_has_pstmt(client, "dangling") == 1); test(od_client_get_pstmt(client, "dangling") == &pstmt); od_client_free(client); } static void test_pstmt_server_hashmap(void) { od_server_t *server = od_server_allocate(1); od_pstmt_t pstmt1; od_pstmt_next_name(&pstmt1); pstmt1.desc.data = "data1"; pstmt1.desc.len = sizeof("data1"); od_pstmt_t pstmt2; od_pstmt_next_name(&pstmt2); pstmt2.desc.data = "data2"; pstmt2.desc.len = sizeof("data2"); test(od_server_has_pstmt(server, &pstmt1) == 0); test(od_server_has_pstmt(server, &pstmt2) == 0); test(od_server_remove_pstmt(server, &pstmt1) == 0); test(od_server_remove_pstmt(server, &pstmt2) == 0); test(od_server_add_pstmt(server, &pstmt1) == 0); test(od_server_has_pstmt(server, &pstmt1) == 1); test(od_server_has_pstmt(server, &pstmt2) == 0); test(od_server_add_pstmt(server, &pstmt2) == 0); test(od_server_has_pstmt(server, &pstmt1) == 1); test(od_server_has_pstmt(server, &pstmt2) == 1); test(od_server_remove_pstmt(server, &pstmt2) == 0); test(od_server_has_pstmt(server, &pstmt1) == 1); test(od_server_has_pstmt(server, &pstmt2) == 0); od_server_pstmts_clear(server); test(od_server_has_pstmt(server, &pstmt1) == 0); test(od_server_has_pstmt(server, &pstmt2) == 0); od_pstmt_t dangling; od_pstmt_next_name(&dangling); dangling.desc.data = "dangling"; dangling.desc.len = sizeof("dangling"); od_pstmt_t danglingcopy; memcpy(&danglingcopy, &dangling, sizeof(od_pstmt_t)); test(od_server_add_pstmt(server, &dangling) == 0); test(od_server_has_pstmt(server, &dangling) == 1); test(od_server_has_pstmt(server, &danglingcopy) == 1); od_server_free(server); } static void test_pstmt_global(void) { mm_hashmap_t *gm = od_global_pstmts_map_create(); od_pstmt_desc_t desc; desc.data = "data"; desc.len = sizeof("data"); od_pstmt_t *pstmt = od_pstmt_create_or_get(gm, desc); 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(od_pstmt_create_or_get(gm, desc) == pstmt); od_global_pstmts_map_free(gm); } static void test_impl(void *a) { (void)a; test_pstmt_client_hashmap(); test_pstmt_server_hashmap(); test_pstmt_global(); } 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.5.1-rc8/sources/tests/odyssey/test_query_processing.c000066400000000000000000000064651517700303500250530ustar00rootroot00000000000000#include #include #include static void deallocate_test(const char *source, int ret, const char *name) { const char *nret; size_t nretlen; int sut = od_parse_deallocate(source, strlen(source), &nret, &nretlen); test(ret == sut); if (ret == 0) { test(nretlen == strlen(name)); test(strncmp(name, nret, nretlen) == 0); } } void odyssey_test_parse_deallocate(void) { deallocate_test("DEALLOCATE foo", 0, "foo"); deallocate_test("deallocate foo", 0, "foo"); deallocate_test("DEALLOCATE foo;", 0, "foo"); deallocate_test(" DEALLOCATE foo", 0, "foo"); deallocate_test("\tDEALLOCATE\tfoo", 0, "foo"); deallocate_test("DEALLOCATE foo ", 0, "foo"); deallocate_test("DEALLOCATE foo ;", 0, "foo"); deallocate_test("DEALLOCATE foo; ", 0, "foo"); deallocate_test(" \n\r\t DEALLOCATE \n foo \r\n", 0, "foo"); deallocate_test("DEALLOCATE PREPARE foo", 0, "foo"); deallocate_test("deallocate prepare foo", 0, "foo"); deallocate_test("DEALLOCATE PREPARE foo;", 0, "foo"); deallocate_test("DEALLOCATE PREPARE foo", 0, "foo"); deallocate_test(" DEALLOCATE PREPARE foo ", 0, "foo"); deallocate_test("DEALLOCATE ALL", 1, NULL); deallocate_test("deallocate all", 1, NULL); deallocate_test("DEALLOCATE ALL;", 1, NULL); deallocate_test("DEALLOCATE ALL ", 1, NULL); deallocate_test(" DEALLOCATE ALL ; ", 1, NULL); deallocate_test("DEALLOCATE PREPARE ALL", 1, NULL); deallocate_test("DEALLOCATE PREPARE ALL;", 1, NULL); deallocate_test("DEALLOCATE \'foo\'", 0, "foo"); deallocate_test("DEALLOCATE \'FooBar\'", 0, "FooBar"); deallocate_test("DEALLOCATE PREPARE \'foo\'", 0, "foo"); deallocate_test("DEALLOCATE \'foo\';", 0, "foo"); deallocate_test("DEALLOCATE \'ALL\'", 0, "ALL"); deallocate_test("DEALLOCATE PREPARE \'ALL\'", 0, "ALL"); deallocate_test("DEALLOCATE \'a\'\'b\'", -1, NULL); deallocate_test("DEALLOCATE PREPARE \'a\'\'b\'", -1, NULL); deallocate_test("DEALLOCATE \'x\'\'y\'\'z\';", -1, NULL); deallocate_test("DEALLOCATE foo_bar", 0, "foo_bar"); deallocate_test("DEALLOCATE foo123", 0, "foo123"); deallocate_test("", -1, NULL); deallocate_test(" ", -1, NULL); deallocate_test("DEALLOCATE", -1, NULL); deallocate_test("DEALLOCATE ", -1, NULL); deallocate_test("DEALLOCATE\t", -1, NULL); deallocate_test("DEALLOCATE\n", -1, NULL); deallocate_test("DEALLOCATE PREPARE", -1, NULL); deallocate_test("DEALLOCATE PREPARE ", -1, NULL); deallocate_test("DEALLOCATE PREPARE;", -1, NULL); deallocate_test("ALLOCATE foo", -1, NULL); deallocate_test("PREPARE foo", -1, NULL); deallocate_test("SELECT 1", -1, NULL); deallocate_test("DEALLOCATE PREPARE pst", 0, "pst"); deallocate_test("DEALLOCATE prepare pst", 0, "pst"); deallocate_test("DEALLOCATE preparepst", 0, "preparepst"); deallocate_test("DEALLOCATE PREPAREpst", 0, "PREPAREpst"); deallocate_test("DEALLOCATE ALL", 1, NULL); deallocate_test("DEALLOCATE all", 1, NULL); deallocate_test("DEALLOCATE allpst", 0, "allpst"); deallocate_test("DEALLOCATE ALLpst", 0, "ALLpst"); deallocate_test("DEALLOCATE prepared pst", -1, NULL); deallocate_test("DEALLOCATE PREPARED pst", -1, NULL); deallocate_test("DEALLOCATE ALL pst", -1, NULL); deallocate_test("DEALLOCATE pst extra", -1, NULL); deallocate_test("DEALLOCATE \'ALL\'", 0, "ALL"); deallocate_test("DEALLOCATE \'PREPARE\'", 0, "PREPARE"); } odyssey-1.5.1-rc8/sources/tests/odyssey/test_tdigest.c000066400000000000000000000114041517700303500231020ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/odyssey/test_util.c000066400000000000000000000020221517700303500224100ustar00rootroot00000000000000#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.5.1-rc8/sources/tests/odyssey_test.c000066400000000000000000000234641517700303500214500ustar00rootroot00000000000000 /* * 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); /* 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_tls0(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_queue(void); extern void machinarium_vrb_benchmark(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_hashmap(void); extern void odyssey_test_pstmt(void); extern void odyssey_test_parse_deallocate(void); extern void odyssey_test_affinity(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(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_tls0); 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_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(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_hashmap); odyssey_test(odyssey_test_pstmt); odyssey_test(odyssey_test_parse_deallocate); odyssey_test(odyssey_test_affinity); odyssey_playground_test(machinarium_test_tsan_simple_race_example); return 0; } odyssey-1.5.1-rc8/sources/thread_global.c000066400000000000000000000015341517700303500203310ustar00rootroot00000000000000/* * 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(); } od_retcode_t od_thread_global_free(od_thread_global *gl) { od_retcode_t rc = od_conn_eject_info_free(gl->info); if (rc != OK_RESPONSE) { return rc; } od_free(gl); return OK_RESPONSE; } odyssey-1.5.1-rc8/sources/tls.c000066400000000000000000000132131517700303500163410ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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: 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; } int od_tls_frontend_accept(od_client_t *client, od_logger_t *logger, od_config_listen_t *config, machine_tls_t *tls) { if (client->startup.is_ssl_request) { 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 %d us", od_io_error(&client->io), machine_time_us() - client->time_accept); return -1; } od_debug(logger, "tls", client, NULL, "ok"); return 0; } /* Client sends cancel request without encryption */ if (client->startup.is_cancel) { return 0; } switch (config->tls_opts->tls_mode) { case OD_CONFIG_TLS_DISABLE: case OD_CONFIG_TLS_ALLOW: break; default: od_log(logger, "tls", client, NULL, "required, closing"); od_frontend_error(client, KIWI_PROTOCOL_VIOLATION, "SSL is required"); return -1; } 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: 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) { od_debug(logger, "tls", NULL, server, "not supported, continue (allow)"); } 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.5.1-rc8/sources/tls_config.c000066400000000000000000000013631517700303500176710ustar00rootroot00000000000000/* * 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; } 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.5.1-rc8/sources/tsa.c000066400000000000000000000036341517700303500163340ustar00rootroot00000000000000/* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #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 = client->config_listen->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; } if (strncmp(hint_var->value, "read-write", hint_var->value_len) == 0) { effective_tsa = OD_TARGET_SESSION_ATTRS_RW; } 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_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.5.1-rc8/sources/worker.c000066400000000000000000000134761517700303500170630ustar00rootroot00000000000000 /* * Odyssey. * * Scalable PostgreSQL connection pooler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PROM_FOUND #include #include #endif 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; } cpu_set_t cpuset; CPU_ZERO(&cpuset); 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 (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)); } } static inline void od_worker(void *arg) { od_worker_t *worker = arg; od_instance_t *instance = worker->global->instance; od_router_t *router = worker->global->router; 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); 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; 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; /* 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_atomic_u32_dec(&router->clients_routing); 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: assert(0); break; } machine_msg_free(msg); } od_thread_global_free(*gl); od_log(&instance->logger, "worker", NULL, NULL, "worker[%d] stopped", 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; } 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.5.1-rc8/sources/xplan.c000066400000000000000000001062211517700303500166630ustar00rootroot00000000000000#include #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 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"; 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_VIRTUAL_ERROR_RESPONSE: case OD_XPLAN_VIRTUAL_PARSE_COMPLETE: case OD_XPLAN_VIRTUAL_CLOSE_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"; 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->pstmt ? delta->pstmt->name : "(null)"); 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 to original msg * - pstmt points to global hashmap */ 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, const od_pstmt_t *pstmt) { 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; } 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, const od_pstmt_t *pstmt) { entry_init_internal(e, OD_XPLAN_FORWARD, original, rewritten, NULL, delta_type, client_pstmt, pstmt); } 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, const 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); } static void entry_init_parse_shadow(od_xplan_entry_t *e, const 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); } 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); } static void entry_init_virtual_parse_complete(od_xplan_entry_t *e, const char *client_pstmt, const 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); } 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); } 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, const 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; } static inline od_frontend_status_t xplan_append_parse(od_xplan_t *xp, const char *client_pstmt, const 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, const 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, const 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; } 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) { mm_vector_clear(&xp->entries); } static const 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 */ 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: /* 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_ALL: /* all pstmts was removed, including this */ return NULL; default: abort(); } } return od_client_get_pstmt(client, pstmt_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: /* 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, const 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; 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); } mm_hashmap_t *global_pstmts = od_instance_get_pstmts_map(instance); od_pstmt_t *pstmt = od_pstmt_create_or_get(global_pstmts, desc); if (pstmt == NULL) { return OD_EOOM; } if (plan_server_has_pstmt(xp, server, pstmt)) { return xplan_append_virtual_parse_complete(xp, pstmt_name, pstmt, msg); } machine_msg_t *pmsg = od_pstmt_parse_of(pstmt); if (pmsg == NULL) { return OD_EOOM; } return xplan_append_parse(xp, pstmt_name, pstmt, msg, pmsg); } 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 */); } const 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); } od_frontend_status_t st = plan_predeploy_pstmt(xp, server, msg, pstmt); if (st != OD_OK) { return st; } machine_msg_t *dmsg = od_pstmt_describe_of(pstmt); if (dmsg == NULL) { return OD_EOOM; } 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_PREPARED_STATEMENT) { return xplan_append_fwd_no_delta(xp, msg, NULL /* no rewrite */); } return xplan_append_virtual_close_complete(xp, pstmt_name, msg); } 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, 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; 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 pstmt_name_len; char *pstmt_name; int rc; rc = kiwi_be_read_bind_stmt_name(data, size, &pstmt_name, &pstmt_name_len); if (rc == -1) { return OD_ECLIENT_PROTOCOL_ERROR; } const 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 remove_all = 0; /* XXX: we must plan discard on Execute, not on Bind */ const od_pstmt_desc_t *desc = &pstmt->desc; if (desc->len >= 7) { if (strncmp(desc->data, "DISCARD", 7) == 0) { od_debug(&instance->logger, "rewrite bind", client, server, "discard detected, invalidate caches"); remove_all = 1; } else { size_t name_len; const char *name = NULL; int rc = od_parse_deallocate(desc->data, strlen(desc->data), &name, &name_len); switch (rc) { case 1: remove_all = 1; break; case 0: /* TODO: support DEALLOCATE name */ break; default: break; } } } int pstmt_start_offset = kiwi_be_bind_opname_offset(data, size); if (pstmt_start_offset < 0) { return OD_ECLIENT_PROTOCOL_ERROR; } 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; } if (!remove_all) { return xplan_append_fwd_no_delta(xp, msg, bmsg); } return xplan_append_fwd(xp, msg, bmsg, OD_XPLAN_DELTA_REMOVE_ALL, pstmt_name, pstmt); } static od_frontend_status_t plan_execute(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_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_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 = 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_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; 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); 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; 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: /* nothing to do */ break; case OD_XPLAN_DELTA_REMOVE_CLIENT_ONLY: od_client_remove_pstmt(client, delta->client_pstmt); break; case OD_XPLAN_DELTA_REMOVE_ALL: od_client_pstmts_clear(client); od_server_pstmts_clear(server); 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 }; assert(sizeof(bytes) == 5); size_t unused; int rc = od_io_write_raw(&client->io, bytes, sizeof(bytes), &unused, timeout_ms); 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 }; assert(sizeof(bytes) == 5); size_t unused; int rc = od_io_write_raw(&client->io, bytes, sizeof(bytes), &unused, timeout_ms); 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); 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_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) { 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); 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 -> * 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); 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_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_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); 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); 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.5.1-rc8/test/000077500000000000000000000000001517700303500146675ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/async-notice/000077500000000000000000000000001517700303500172635ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/async-notice/Dockerfile000066400000000000000000000004571517700303500212630ustar00rootroot00000000000000FROM 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.5.1-rc8/test/async-notice/docker-compose.yml000066400000000000000000000013241517700303500227200ustar00rootroot00000000000000services: 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.5.1-rc8/test/async-notice/entrypoint.sh000077500000000000000000000005101517700303500220310ustar00rootroot00000000000000#!/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.5.1-rc8/test/async-notice/go.mod000066400000000000000000000003671517700303500203770ustar00rootroot00000000000000module 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.5.1-rc8/test/async-notice/go.sum000066400000000000000000000043731517700303500204250ustar00rootroot00000000000000github.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.5.1-rc8/test/async-notice/main_test.go000066400000000000000000000034671517700303500216070ustar00rootroot00000000000000package 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.5.1-rc8/test/async-notice/test.conf000066400000000000000000000011361517700303500211120ustar00rootroot00000000000000unix_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 coroutine_stack_size 16 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.5.1-rc8/test/build-test/000077500000000000000000000000001517700303500167435ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/build-test/Dockerfile.debian000066400000000000000000000015521517700303500221610ustar00rootroot00000000000000ARG 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 \ devscripts debhelper COPY . /build_dir WORKDIR /build_dir ENTRYPOINT [ "bash", "-c", "dpkg-buildpackage -us -uc -b" ] odyssey-1.5.1-rc8/test/build-test/Dockerfile.fedora000066400000000000000000000002341517700303500221730ustar00rootroot00000000000000FROM fedora:40 RUN dnf install -y cmake gcc make git openssl-devel systemd-devel COPY . /workdir WORKDIR /workdir RUN make clean RUN make build_release odyssey-1.5.1-rc8/test/build-test/Dockerfile.oraclelinux000066400000000000000000000003351517700303500232620ustar00rootroot00000000000000ARG version=8 FROM oraclelinux:${version} RUN dnf install -y make gcc cmake git openssl-devel systemd-devel COPY . /build_dir WORKDIR /build_dir RUN make clean ENTRYPOINT [ "bash", "-c", "make $ODYSSEY_BUILD_TYPE" ] odyssey-1.5.1-rc8/test/build-test/Dockerfile.ubuntu000066400000000000000000000016071517700303500222620ustar00rootroot00000000000000ARG 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 \ devscripts debhelper COPY . /build_dir WORKDIR /build_dir ENTRYPOINT [ "bash", "-c", "dpkg-buildpackage -us -uc -b" ] odyssey-1.5.1-rc8/test/drivers/000077500000000000000000000000001517700303500163455ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/jdbc/000077500000000000000000000000001517700303500172475ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/jdbc/docker-compose.yml000066400000000000000000000015661517700303500227140ustar00rootroot00000000000000services: 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.5.1-rc8/test/drivers/userver/000077500000000000000000000000001517700303500200405ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/userver/CMakeLists.txt000066400000000000000000000006571517700303500226100ustar00rootroot00000000000000cmake_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.5.1-rc8/test/drivers/userver/Dockerfile.userver-test000066400000000000000000000004611517700303500245020ustar00rootroot00000000000000FROM 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.5.1-rc8/test/drivers/userver/configs/000077500000000000000000000000001517700303500214705ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/userver/configs/config.yaml000066400000000000000000000023011517700303500236150ustar00rootroot00000000000000components_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.5.1-rc8/test/drivers/userver/configs/dynamic_config_fallback.json000066400000000000000000000004061517700303500271530ustar00rootroot00000000000000{ "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.5.1-rc8/test/drivers/userver/docker-compose.yml000066400000000000000000000017041517700303500234770ustar00rootroot00000000000000services: 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.5.1-rc8/test/drivers/userver/odyssey.conf000066400000000000000000000025331517700303500224110ustar00rootroot00000000000000daemonize 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.5.1-rc8/test/drivers/userver/scripts/000077500000000000000000000000001517700303500215275ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/userver/scripts/run-all.sh000066400000000000000000000017041517700303500234370ustar00rootroot00000000000000#!/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.5.1-rc8/test/drivers/userver/src/000077500000000000000000000000001517700303500206275ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/userver/src/create_note.cpp000066400000000000000000000030501517700303500236210ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/create_note.hpp000066400000000000000000000012701517700303500236300ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/get_note.cpp000066400000000000000000000026561517700303500231500ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/get_note.hpp000066400000000000000000000012541517700303500231460ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/main.cpp000066400000000000000000000016631517700303500222650ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/ping.cpp000066400000000000000000000003361517700303500222720ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/ping.hpp000066400000000000000000000007121517700303500222750ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/portal_fill.cpp000066400000000000000000000041271517700303500236460ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/portal_fill.hpp000066400000000000000000000012701517700303500236470ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/portal_scan.cpp000066400000000000000000000106601517700303500236430ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/src/portal_scan.hpp000066400000000000000000000012701517700303500236450ustar00rootroot00000000000000#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.5.1-rc8/test/drivers/userver/tests/000077500000000000000000000000001517700303500212025ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/drivers/userver/tests/conftest.py000066400000000000000000000003231517700303500233770ustar00rootroot00000000000000import 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.5.1-rc8/test/drivers/userver/tests/test_notes.py000066400000000000000000000011731517700303500237450ustar00rootroot00000000000000import 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.5.1-rc8/test/drivers/userver/tests/test_portal_scan.py000066400000000000000000000026531517700303500251260ustar00rootroot00000000000000import 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.5.1-rc8/test/drivers/userver/userfaultfd.json000066400000000000000000000000661517700303500232610ustar00rootroot00000000000000{"names": ["userfaultfd"], "action": "SCMP_ACT_ALLOW"}odyssey-1.5.1-rc8/test/functional/000077500000000000000000000000001517700303500170315ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/Dockerfile000066400000000000000000000151701517700303500210270ustar00rootroot00000000000000FROM 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/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 \ 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/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 \ 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/archive.ubuntu.com/mirror.yandex.ru/g' /etc/apt/sources.list 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-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 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.5.1-rc8/test/functional/bin/000077500000000000000000000000001517700303500176015ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/bin/ody-reload000077500000000000000000000000721517700303500215650ustar00rootroot00000000000000#!/bin/bash old_pid=$(pgrep odyssey) kill -s HUP $old_pid odyssey-1.5.1-rc8/test/functional/bin/ody-restart000077500000000000000000000001461517700303500220050ustar00rootroot00000000000000#!/bin/bash #old_pid=$(pgrep odyssey) /usr/bin/odyssey /etc/odyssey/odyssey.conf #kill -USR2 $old_pid odyssey-1.5.1-rc8/test/functional/bin/ody-start000077500000000000000000000000671517700303500214600ustar00rootroot00000000000000#!/bin/bash /usr/bin/odyssey /etc/odyssey/odyssey.conf odyssey-1.5.1-rc8/test/functional/bin/ody-stop000077500000000000000000000060661517700303500213150ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/bin/setup000077500000000000000000000225711517700303500206760ustar00rootroot00000000000000#!/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 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 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 } # 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 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.5.1-rc8/test/functional/bin/start-pg000077500000000000000000000010541517700303500212700ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/bin/teardown000077500000000000000000000003461517700303500213550ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/docker-compose.yml000066400000000000000000000024231517700303500224670ustar00rootroot00000000000000services: 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.5.1-rc8/test/functional/odyssey.conf000066400000000000000000000015311517700303500213770ustar00rootroot00000000000000 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/requirements.txt000066400000000000000000000000071517700303500223120ustar00rootroot00000000000000pytest odyssey-1.5.1-rc8/test/functional/test_functional.py000066400000000000000000000026541517700303500226130ustar00rootroot00000000000000import 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.5.1-rc8/test/functional/tests/000077500000000000000000000000001517700303500201735ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/auth_query/000077500000000000000000000000001517700303500223615ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/auth_query/config.conf000066400000000000000000000023061517700303500244760ustar00rootroot00000000000000storage "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" } } 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 yes log_session yes log_stats no log_query no coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 target_session_attrs "read-write" } odyssey-1.5.1-rc8/test/functional/tests/auth_query/runner.sh000077500000000000000000000020501517700303500242260ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/auth_query/config.conf sleep 1 timeout 20s 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 10 --connect --no-vacuum -j2 -c2 --progress 1 || { echo "ERROR: failed backend auth with correct password" sleep 1 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 20s pgbench 'host=localhost port=6432 user=auth_query_user_md5 dbname=auth_query_db password=passwd' -f /tests/auth_query/select.sql -T 10 --connect --no-vacuum -j2 -c2 --progress 1 || { echo "ERROR: failed backend auth with correct password" sleep 1 cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop odyssey-1.5.1-rc8/test/functional/tests/auth_query/select.sql000066400000000000000000000000121517700303500243520ustar00rootroot00000000000000select 42;odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/000077500000000000000000000000001517700303500237545ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/.gitignore000066400000000000000000000000121517700303500257350ustar00rootroot00000000000000results/* odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/expected/000077500000000000000000000000001517700303500255555ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/expected/1.out000066400000000000000000000005201517700303500264430ustar00rootroot00000000000000drop 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) odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/odyssey.conf000066400000000000000000000011111517700303500263140ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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 "postgres" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_discard no pool_smart_discard yes } } odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/runner.sh000077500000000000000000000013331517700303500256240ustar00rootroot00000000000000#!/bin/bash set -ex check_file() { local name=$1 local user=$2 cat /tests/broken_cached_plan/sql/$name.sql | psql "host=localhost port=6432 user=$user dbname=postgres" --echo-all --no-psqlrc --quiet > /tests/broken_cached_plan/results/$name.out 2>&1 || { cat /tests/broken_cached_plan/results/$name.out exit 1 } diff /tests/broken_cached_plan/expected/$name.out /tests/broken_cached_plan/results/$name.out || { cat /tests/broken_cached_plan/results/$name.out xxd /tests/broken_cached_plan/results/$name.out exit 1 } } mkdir -p /tests/broken_cached_plan/results/ /usr/bin/odyssey /tests/broken_cached_plan/odyssey.conf sleep 1 check_file '1' postgres ody-stop odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/sql/000077500000000000000000000000001517700303500245535ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/broken_cached_plan/sql/1.sql000066400000000000000000000002721517700303500254350ustar00rootroot00000000000000drop 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; odyssey-1.5.1-rc8/test/functional/tests/broken_conn/000077500000000000000000000000001517700303500224705ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/broken_conn/.gitignore000066400000000000000000000000121517700303500244510ustar00rootroot00000000000000results/* odyssey-1.5.1-rc8/test/functional/tests/broken_conn/expected/000077500000000000000000000000001517700303500242715ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/broken_conn/expected/1.out000066400000000000000000000003651517700303500251660ustar00rootroot00000000000000select 43; ?column? ---------- 43 (1 row) set application_name to 'kill_me'; \! /tests/broken_conn/kill_server.sh pg_terminate_backend ---------------------- t (1 row) \! sleep 1 select 87; ?column? ---------- 87 (1 row) odyssey-1.5.1-rc8/test/functional/tests/broken_conn/kill_server.sh000077500000000000000000000002661517700303500253540ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/broken_conn/odyssey.conf000066400000000000000000000011101517700303500250270ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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 "postgres" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_discard no pool_smart_discard no } } odyssey-1.5.1-rc8/test/functional/tests/broken_conn/runner.sh000077500000000000000000000012341517700303500243400ustar00rootroot00000000000000#!/bin/bash set -ex check_file() { local name=$1 local user=$2 cat /tests/broken_conn/sql/$name.sql | psql "host=localhost port=6432 user=$user dbname=postgres" --echo-all --no-psqlrc --quiet > /tests/broken_conn/results/$name.out 2>&1 || { cat /tests/broken_conn/results/$name.out exit 1 } diff /tests/broken_conn/expected/$name.out /tests/broken_conn/results/$name.out || { cat /tests/broken_conn/results/$name.out xxd /tests/broken_conn/results/$name.out exit 1 } } mkdir -p /tests/broken_conn/results/ /usr/bin/odyssey /tests/broken_conn/odyssey.conf sleep 1 check_file '1' postgres ody-stop odyssey-1.5.1-rc8/test/functional/tests/broken_conn/sql/000077500000000000000000000000001517700303500232675ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/broken_conn/sql/1.sql000066400000000000000000000001551517700303500241510ustar00rootroot00000000000000select 43; set application_name to 'kill_me'; \! /tests/broken_conn/kill_server.sh \! sleep 1 select 87; odyssey-1.5.1-rc8/test/functional/tests/cancel-leak/000077500000000000000000000000001517700303500223325ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/cancel-leak/cancel-leak.conf000066400000000000000000000014501517700303500253400ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 workers 2 resolvers 1 client_max_routing 16 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/cancel-leak/evil_proxy.py000066400000000000000000000070441517700303500251110ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/cancel-leak/runner.sh000077500000000000000000000113011517700303500241760ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/cancel-leak/send_cancel.py000066400000000000000000000072061517700303500251470ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/cancel/000077500000000000000000000000001517700303500214205ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/cancel/cancel.conf000066400000000000000000000007601517700303500235170ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/cancel/runner.sh000077500000000000000000000026161517700303500232750ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/cascade/000077500000000000000000000000001517700303500215565ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/cascade/odyssey-gateway.conf000066400000000000000000000020341517700303500255620ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/cascade/odyssey-root1.conf000066400000000000000000000015251517700303500251710ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/cascade/odyssey-root2.conf000066400000000000000000000015251517700303500251720ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/cascade/runner.sh000077500000000000000000000055501517700303500234330ustar00rootroot00000000000000#!/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 } root1_client_processed=`cat /var/log/odyssey.root1.log | grep -oP 'clients_processed: \d+' | tail -n 1 | grep -oP '\d+'` root2_client_processed=`cat /var/log/odyssey.root2.log | grep -oP 'clients_processed: \d+' | tail -n 1 | grep -oP '\d+'` python3 -c 'import sys; \ root1 = int(sys.argv[-1]); \ root2 = int(sys.argv[-2]); \ diff = abs(root1 - root2); \ exit(0 if diff <= min(root1, root2) else 1)' $root1_client_processed $root2_client_processed || { echo "connects should be distributed equally (some kind of) between roots" exit 1 } sleep 1 ody-stop $gateway_pid ody-stopodyssey-1.5.1-rc8/test/functional/tests/config-validation/000077500000000000000000000000001517700303500235705ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/000077500000000000000000000000001517700303500252205ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/auth_query/000077500000000000000000000000001517700303500274065ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/auth_query/invalid/000077500000000000000000000000001517700303500310345ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000006261517700303500343650ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006321517700303500343630ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005631517700303500343670ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/auth_query/valid/000077500000000000000000000000001517700303500305055ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000006751517700303500340420ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/authentication/000077500000000000000000000000001517700303500302375ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/authentication/invalid/000077500000000000000000000000001517700303500316655ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005121517700303500352100ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005241517700303500352140ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005121517700303500352120ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/authentication/valid/000077500000000000000000000000001517700303500313365ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005131517700303500346620ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005131517700303500347420ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005141517700303500346640ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005651517700303500346730ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005561517700303500346740ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006741517700303500346760ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000007061517700303500346730ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005701517700303500346730ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000007401517700303500346730ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000007521517700303500346770ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/coroutine_stack_size/000077500000000000000000000000001517700303500314465ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/coroutine_stack_size/invalid/000077500000000000000000000000001517700303500330745ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001351517700303500364200ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000001351517700303500364210ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000001361517700303500364230ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000001411517700303500364200ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000010311517700303500364200ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/coroutine_stack_size/valid/000077500000000000000000000000001517700303500325455ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005041517700303500360710ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005051517700303500360730ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000010311517700303500360670ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/group_rules/000077500000000000000000000000001517700303500275665ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/group_rules/invalid/000077500000000000000000000000001517700303500312145ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000006631517700303500345460ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000007511517700303500345450ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000007561517700303500345530ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/group_rules/valid/000077500000000000000000000000001517700303500306655ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000010351517700303500342110ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/listen_empty/000077500000000000000000000000001517700303500277345ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_empty/invalid/000077500000000000000000000000001517700303500313625ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000000631517700303500347060ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_empty/invalidworkers 0 log_format "%p %t %l [%i %s] (%c) %m\n" odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_empty/valid/000077500000000000000000000000001517700303500310335ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004671517700303500343670ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/000077500000000000000000000000001517700303500274005ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/invalid/000077500000000000000000000000001517700303500310265ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001241517700303500343500ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "" } odyssey-test2.conf000066400000000000000000000001271517700303500343540ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "123" } odyssey-test3.conf000066400000000000000000000001321517700303500343510ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls "disabl" } odyssey-test4.conf000066400000000000000000000001311517700303500343510ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" tls disable } odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/listen_tls/valid/000077500000000000000000000000001517700303500304775ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005021517700303500340210ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005001517700303500340200ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005021517700303500340230ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005041517700303500340260ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005061517700303500340310ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/log_format/000077500000000000000000000000001517700303500273515ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/log_format/invalid/000077500000000000000000000000001517700303500307775ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000000341517700303500343210ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/log_format/invalidlisten { host "*" } odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/log_format/valid/000077500000000000000000000000001517700303500304505ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004551517700303500340010ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/pool_reserve_prepared_statement/000077500000000000000000000000001517700303500336725ustar00rootroot00000000000000invalid/000077500000000000000000000000001517700303500352415ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/pool_reserve_prepared_statementodyssey-test1.conf000066400000000000000000000006421517700303500406470ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005771517700303500406570ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006471517700303500406560ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006501517700303500406510ustar00rootroot00000000000000odyssey-1.5.1-rc8/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/000077500000000000000000000000001517700303500347125ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/pool_reserve_prepared_statementodyssey-test1.conf000066400000000000000000000006411517700303500403170ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006001517700303500403130ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000007111517700303500403170ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/pool_routing/000077500000000000000000000000001517700303500277405ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/pool_routing/invalid/000077500000000000000000000000001517700303500313665ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005561517700303500347210ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/pool_routing/valid/000077500000000000000000000000001517700303500310375ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005631517700303500343700ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005711517700303500343700ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/pool_type/000077500000000000000000000000001517700303500272325ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/pool_type/invalid/000077500000000000000000000000001517700303500306605ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004541517700303500342100ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005071517700303500342100ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/pool_type/valid/000077500000000000000000000000001517700303500303315ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/pool_type/valid/odyssey-test1.conf000066400000000000000000000005131517700303500337340ustar00rootroot00000000000000unix_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.5.1-rc8/test/functional/tests/config-validation/configs/pool_type/valid/odyssey-test2.conf000066400000000000000000000005171517700303500337410ustar00rootroot00000000000000unix_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.5.1-rc8/test/functional/tests/config-validation/configs/pool_type/valid/odyssey-test3.conf000066400000000000000000000005151517700303500337400ustar00rootroot00000000000000unix_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.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/000077500000000000000000000000001517700303500272445ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/invalid/000077500000000000000000000000001517700303500306725ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001221517700303500342120ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/invalidresolvers 0 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test2.conf000066400000000000000000000001241517700303500342150ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/invalidresolvers -10 log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-test3.conf000066400000000000000000000001251517700303500342170ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/invalidresolvers "10" log_format "%p %t %l [%i %s] (%c) %m\n" listen { host "*" } odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/valid/000077500000000000000000000000001517700303500303435ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/valid/odyssey-test1.conf000066400000000000000000000004721517700303500337520ustar00rootroot00000000000000resolvers 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.5.1-rc8/test/functional/tests/config-validation/configs/resolvers/valid/odyssey-test2.conf000066400000000000000000000004721517700303500337530ustar00rootroot00000000000000resolvers 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.5.1-rc8/test/functional/tests/config-validation/configs/rules_empty/000077500000000000000000000000001517700303500275705ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/rules_empty/invalid/000077500000000000000000000000001517700303500312165ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000003541517700303500345450ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000002521517700303500345430ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/rules_empty/valid/000077500000000000000000000000001517700303500306675ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005131517700303500342130ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/storage_name/000077500000000000000000000000001517700303500276645ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/storage_name/invalid/000077500000000000000000000000001517700303500313125ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004411517700303500346360ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005041517700303500346370ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/storage_name/valid/000077500000000000000000000000001517700303500307635ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005131517700303500343070ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/storage_tls/000077500000000000000000000000001517700303500275465ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/storage_tls/invalid/000077500000000000000000000000001517700303500311745ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000002131517700303500345150ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000002161517700303500345210ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000002151517700303500345210ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/storage_tls/valid/000077500000000000000000000000001517700303500306455ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004601517700303500341720ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000004561517700303500342000ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000004601517700303500341740ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000004621517700303500341770ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000004641517700303500342020ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/storage_type/000077500000000000000000000000001517700303500277255ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/storage_type/invalid/000077500000000000000000000000001517700303500313535ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000000641517700303500347000ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/storage_type/invalidlog_format "%p %t %l [%i %s] (%c) %m\n" listen { } odyssey-test2.conf000066400000000000000000000001721517700303500347010ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000001441517700303500347010ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006201517700303500347010ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006171517700303500347100ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006231517700303500347060ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000006171517700303500347120ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/storage_type/valid/000077500000000000000000000000001517700303500310245ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000004541517700303500343540ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000005131517700303500343510ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000004321517700303500343520ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/unix_socket_mode/000077500000000000000000000000001517700303500305575ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/unix_socket_mode/invalid/000077500000000000000000000000001517700303500322055ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000001351517700303500355310ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/unix_socket_mode/valid/000077500000000000000000000000001517700303500316565ustar00rootroot00000000000000odyssey-test1.conf000066400000000000000000000005341517700303500352050ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.conf000066400000000000000000000004541517700303500352070ustar00rootroot00000000000000odyssey-1.5.1-rc8/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.5.1-rc8/test/functional/tests/config-validation/configs/workers/000077500000000000000000000000001517700303500267145ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/workers/invalid/000077500000000000000000000000001517700303500303425ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/workers/invalid/odyssey-test1.conf000066400000000000000000000004701517700303500337470ustar00rootroot00000000000000workers 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.5.1-rc8/test/functional/tests/config-validation/configs/workers/invalid/odyssey-test2.conf000066400000000000000000000004721517700303500337520ustar00rootroot00000000000000workers -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.5.1-rc8/test/functional/tests/config-validation/configs/workers/invalid/odyssey-test3.conf000066400000000000000000000004741517700303500337550ustar00rootroot00000000000000workers "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.5.1-rc8/test/functional/tests/config-validation/configs/workers/valid/000077500000000000000000000000001517700303500300135ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/configs/workers/valid/odyssey-test1.conf000066400000000000000000000004701517700303500334200ustar00rootroot00000000000000workers 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.5.1-rc8/test/functional/tests/config-validation/configs/workers/valid/odyssey-test2.conf000066400000000000000000000004701517700303500334210ustar00rootroot00000000000000workers 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.5.1-rc8/test/functional/tests/config-validation/configs/workers/valid/odyssey-test3.conf000066400000000000000000000004741517700303500334260ustar00rootroot00000000000000workers "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.5.1-rc8/test/functional/tests/config-validation/go.mod000066400000000000000000000000441517700303500246740ustar00rootroot00000000000000module config-validation go 1.23.4 odyssey-1.5.1-rc8/test/functional/tests/config-validation/go.sum000066400000000000000000000000001517700303500247110ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/pkg/000077500000000000000000000000001517700303500243515ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/config-validation/pkg/main.go000066400000000000000000000032041517700303500256230ustar00rootroot00000000000000package 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", } for _, test := range tests { makeTests(test, true) makeTests(test, false) } } func main() { runTests() } odyssey-1.5.1-rc8/test/functional/tests/config-validation/runner.sh000077500000000000000000000001441517700303500254370ustar00rootroot00000000000000#!/usr/bin/env bash set -ex ody-start sleep 1 /tests/config-validation/config-validation ody-stop odyssey-1.5.1-rc8/test/functional/tests/copy/000077500000000000000000000000001517700303500211455ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/copy/config.conf000066400000000000000000000021751517700303500232660ustar00rootroot00000000000000pid_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 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 "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.5.1-rc8/test/functional/tests/copy/config_without_pstmt.conf000066400000000000000000000021741517700303500262770ustar00rootroot00000000000000pid_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 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 "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.5.1-rc8/test/functional/tests/copy/runner.sh000077500000000000000000000060761517700303500230260ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/external_auth/000077500000000000000000000000001517700303500230365ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/external_auth/external_auth.conf000066400000000000000000000010341517700303500265460ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/external_auth/go.mod000066400000000000000000000000441517700303500241420ustar00rootroot00000000000000module external-auth-agent go 1.21 odyssey-1.5.1-rc8/test/functional/tests/external_auth/pkg/000077500000000000000000000000001517700303500236175ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/external_auth/pkg/external-auth-agent.go000066400000000000000000000060721517700303500300300ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/external_auth/runner.sh000077500000000000000000000042131517700303500247060ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/gorm/000077500000000000000000000000001517700303500211375ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/gorm/runner.sh000077500000000000000000000005401517700303500230060ustar00rootroot00000000000000set -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.5.1-rc8/test/functional/tests/graceful-shutdown-timeout/000077500000000000000000000000001517700303500253205ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/graceful-shutdown-timeout/config.conf000066400000000000000000000011271517700303500274350ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 graceful_shutdown_timeout_ms 5000 conn_drop_options { drop_enabled no } listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/graceful-shutdown-timeout/runner.sh000077500000000000000000000010241517700303500271650ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/group/000077500000000000000000000000001517700303500213275ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/group/config.conf000066400000000000000000000063371517700303500234540ustar00rootroot00000000000000listen { 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.5.1-rc8/test/functional/tests/group/runner.sh000077500000000000000000000064331517700303500232050ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/hba/000077500000000000000000000000001517700303500207255ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/hba/common.conf000066400000000000000000000020731517700303500230660ustar00rootroot00000000000000storage "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" } } 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.5.1-rc8/test/functional/tests/hba/odyssey_hba.conf000066400000000000000000000002741517700303500241100ustar00rootroot00000000000000local 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 odyssey-1.5.1-rc8/test/functional/tests/hba/runner.sh000077500000000000000000000040251517700303500225760ustar00rootroot00000000000000#!/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" cat /var/log/odyssey.log 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" cat /var/log/odyssey.log 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 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" cat /var/log/odyssey.log 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" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } 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" cat /var/log/odyssey.log 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" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop odyssey-1.5.1-rc8/test/functional/tests/hba/tcp.conf000066400000000000000000000001021517700303500223530ustar00rootroot00000000000000include "/tests/hba/common.conf" listen { host "*" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/hba/unix.conf000066400000000000000000000000701517700303500225540ustar00rootroot00000000000000include "/tests/hba/common.conf" listen { port 6432 } odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/000077500000000000000000000000001517700303500226565ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/.gitignore000066400000000000000000000000121517700303500246370ustar00rootroot00000000000000results/* odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/expected/000077500000000000000000000000001517700303500244575ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/expected/session.out000066400000000000000000000007501517700303500266750ustar00rootroot00000000000000-- no connection acquired \! sleep 3 -- long active query works select pg_sleep(6); pg_sleep ---------- (1 row) select 43; ?column? ---------- 43 (1 row) \! sleep 6 select 87; FATAL: odyssey: cXXXXXXXXXXXX: Odyssey has dropped the connection DETAIL: Connection was dropped by Odyssey due to idle timeout server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. connection to server was lost odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/expected/tx.out000066400000000000000000000010261517700303500256420ustar00rootroot00000000000000begin; 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; FATAL: odyssey: cXXXXXXXXXXXX: Odyssey has dropped the connection DETAIL: Connection was dropped by Odyssey due to idle in transaction timeout server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. connection to server was lost odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/odyssey.conf000066400000000000000000000014221517700303500252230ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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 "tuser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "transaction" pool_client_idle_timeout 2 pool_idle_in_transaction_timeout 4 } user "suser" { authentication "none" storage "postgres_server" storage_user "postgres" pool "session" pool_client_idle_timeout 4 pool_idle_in_transaction_timeout 2 } } odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/runner.sh000077500000000000000000000017041517700303500245300ustar00rootroot00000000000000#!/bin/bash set -ex check_file() { local name=$1 local user=$2 cat /tests/idle_timeout/sql/$name.sql | psql "host=localhost port=6432 user=$user dbname=postgres" --echo-all --no-psqlrc --quiet > /tests/idle_timeout/results/$name.out 2>&1 && { # ignore error - the connection reset is expected # exact error will be cheched at diff step anyway cat /tests/idle_timeout/results/$name.out exit 1 } # replace CID with XXX, it changes from run to run anyway sed -E -i 's/c[0-9a-f]{12}/cXXXXXXXXXXXX/g' /tests/idle_timeout/results/$name.out diff /tests/idle_timeout/expected/$name.out /tests/idle_timeout/results/$name.out || { cat /tests/idle_timeout/results/$name.out xxd /tests/idle_timeout/results/$name.out exit 1 } } mkdir -p /tests/idle_timeout/results/ /usr/bin/odyssey /tests/idle_timeout/odyssey.conf sleep 1 check_file tx tuser check_file session suser ody-stop odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/sql/000077500000000000000000000000001517700303500234555ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/sql/session.sql000066400000000000000000000001711517700303500256600ustar00rootroot00000000000000-- no connection acquired \! sleep 3 -- long active query works select pg_sleep(6); select 43; \! sleep 6 select 87; odyssey-1.5.1-rc8/test/functional/tests/idle_timeout/sql/tx.sql000066400000000000000000000001711517700303500246300ustar00rootroot00000000000000begin; select 53; \! sleep 1 select 73; -- long op is not broken select pg_sleep(6); \! sleep 6 select 76; commit; odyssey-1.5.1-rc8/test/functional/tests/invalid_log_file/000077500000000000000000000000001517700303500234615ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/invalid_log_file/config.conf000066400000000000000000000013101517700303500255700ustar00rootroot00000000000000 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.5.1-rc8/test/functional/tests/invalid_log_file/runner.sh000077500000000000000000000003101517700303500253230ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/lagpolling/000077500000000000000000000000001517700303500223235ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/lagpolling/lag-conf.conf000066400000000000000000000041651517700303500246660ustar00rootroot00000000000000pid_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 coroutine_stack_size 8 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 } } 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.5.1-rc8/test/functional/tests/lagpolling/runner.sh000077500000000000000000000014161517700303500241750ustar00rootroot00000000000000#!/bin/bash -x set -eux /usr/bin/odyssey /tests/lagpolling/lag-conf.conf sleep 3 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 10/catchup_timeout 1000/g' /tests/lagpolling/lag-conf.conf PGPASSWORD=lolol psql -h localhost -p6432 -dconsole -Uuser1 -c 'reload' for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' || exit 1 done service postgresql restart || true sleep 1 PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' || exit 1 # test bad backend connections closed properly for _ in $(seq 1 3); do PGPASSWORD=lolol psql -h localhost -p6432 -dpostgres -Uuser1 -c 'select 3' || exit 1 done ody-stop odyssey-1.5.1-rc8/test/functional/tests/ldap/000077500000000000000000000000001517700303500211135ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/ldap/odyssey.conf000066400000000000000000000057411517700303500234700ustar00rootroot00000000000000 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/ldap/runner.sh000077500000000000000000000050631517700303500227670ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/ldap/usr1.ldif000066400000000000000000000004661517700303500226530ustar00rootroot00000000000000dn: 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.5.1-rc8/test/functional/tests/ldap/usr3.ldif000066400000000000000000000004701517700303500226500ustar00rootroot00000000000000dn: 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.5.1-rc8/test/functional/tests/ldap/usr4.ldif000066400000000000000000000007631517700303500226560ustar00rootroot00000000000000dn: 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.5.1-rc8/test/functional/tests/min_pool_size/000077500000000000000000000000001517700303500230415ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/min_pool_size/config.conf000066400000000000000000000013351517700303500251570ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/min_pool_size/runner.sh000077500000000000000000000026651517700303500247220ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/min_pool_size_reload/000077500000000000000000000000001517700303500243675ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/min_pool_size_reload/config.conf000066400000000000000000000013371517700303500265070ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/min_pool_size_reload/runner.sh000077500000000000000000000017331517700303500262430ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/npgsql_compat/000077500000000000000000000000001517700303500230425ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/npgsql_compat/config.conf000066400000000000000000000015001517700303500251520ustar00rootroot00000000000000daemonize 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 } 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.5.1-rc8/test/functional/tests/npgsql_compat/npgsql.sln000066400000000000000000000024561517700303500250730ustar00rootroot00000000000000 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.5.1-rc8/test/functional/tests/npgsql_compat/runner.sh000077500000000000000000000006051517700303500247130ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/npgsql_compat/config.conf /tests/npgsql_compat/NpgsqlOdysseyScram.Console || { echo "ERROR: npgsql-compat tests failed" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop || { cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } exit 0 odyssey-1.5.1-rc8/test/functional/tests/npgsql_compat/src/000077500000000000000000000000001517700303500236315ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console/000077500000000000000000000000001517700303500310445ustar00rootroot00000000000000NpgsqlOdysseyScram.Console.csproj000066400000000000000000000005141517700303500374220ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console Exe net10.0 enable enable odyssey-1.5.1-rc8/test/functional/tests/npgsql_compat/src/NpgsqlOdysseyScram.Console/Program.cs000066400000000000000000000020671517700303500330070ustar00rootroot00000000000000using 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.5.1-rc8/test/functional/tests/ody_integration_test/000077500000000000000000000000001517700303500244305ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/ody_integration_test/go.mod000066400000000000000000000004611517700303500255370ustar00rootroot00000000000000module 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.5.1-rc8/test/functional/tests/ody_integration_test/go.sum000066400000000000000000000061041517700303500255640ustar00rootroot00000000000000filippo.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.5.1-rc8/test/functional/tests/ody_integration_test/pkg/000077500000000000000000000000001517700303500252115ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/ody_integration_test/pkg/client-server.go000066400000000000000000000132611517700303500303250ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/ody_integration_test/pkg/cores.go000066400000000000000000000032271517700303500266570ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/ody_integration_test/pkg/main.go000066400000000000000000000005451517700303500264700ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/ody_integration_test/pkg/pkg-sync.go000066400000000000000000000021371517700303500272760ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/ody_integration_test/pkg/showerrs.go000066400000000000000000000060751517700303500274240ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/ody_integration_test/pkg/util.go000066400000000000000000000112331517700303500265150ustar00rootroot00000000000000package 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.5.1-rc8/test/functional/tests/ody_integration_test/runner.sh000077500000000000000000000001371517700303500263010ustar00rootroot00000000000000#!/usr/bin/env bash set -ex /tests/ody_integration_test/ody_integration_test sleep 5 ody-stopodyssey-1.5.1-rc8/test/functional/tests/online-restart/000077500000000000000000000000001517700303500231415ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/online-restart/odyssey.conf000066400000000000000000000011261517700303500255070ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/online-restart/runner.sh000077500000000000000000000062031517700303500250120ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/online-restart/sleep.sql000066400000000000000000000000241517700303500247660ustar00rootroot00000000000000select pg_sleep(0.5)odyssey-1.5.1-rc8/test/functional/tests/pause-resume/000077500000000000000000000000001517700303500226065ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pause-resume/runner.sh000077500000000000000000000032341517700303500244600ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/pause-resume/session.conf000066400000000000000000000012531517700303500251410ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/pause-resume/transaction.conf000066400000000000000000000012571517700303500260070ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/pg/000077500000000000000000000000001517700303500206015ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pg/pg_hba-test.conf000066400000000000000000000111361517700303500236470ustar00rootroot00000000000000# 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.5.1-rc8/test/functional/tests/pg/runner.sh000077500000000000000000000000441517700303500224470ustar00rootroot00000000000000# TODO: rewrite this folder exit 0 odyssey-1.5.1-rc8/test/functional/tests/pgoptions/000077500000000000000000000000001517700303500222155ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pgoptions/.gitignore000066400000000000000000000000121517700303500241760ustar00rootroot00000000000000results/* odyssey-1.5.1-rc8/test/functional/tests/pgoptions/expected/000077500000000000000000000000001517700303500240165ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pgoptions/expected/invalid_parameter.out000066400000000000000000000000001517700303500302230ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pgoptions/expected/role.out000066400000000000000000000000461517700303500255100ustar00rootroot00000000000000show role; role ------ zz (1 row) odyssey-1.5.1-rc8/test/functional/tests/pgoptions/expected/search_path_injection.out000066400000000000000000000004311517700303500310700ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/pgoptions/expected/search_paths.out000066400000000000000000000001451517700303500272130ustar00rootroot00000000000000show search_path; search_path ------------------------ "$user", public, popis (1 row) odyssey-1.5.1-rc8/test/functional/tests/pgoptions/expected/several_params.out000066400000000000000000000003531517700303500275540ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/pgoptions/expected/spqrguard.out000066400000000000000000000004531517700303500265610ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/pgoptions/odyssey.conf000066400000000000000000000010751517700303500245660ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/pgoptions/runner.sh000077500000000000000000000021341517700303500240650ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/pgoptions/sql/000077500000000000000000000000001517700303500230145ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pgoptions/sql/invalid_parameter.sql000066400000000000000000000000521517700303500272200ustar00rootroot00000000000000raise exception 'This should not be run'; odyssey-1.5.1-rc8/test/functional/tests/pgoptions/sql/role.sql000066400000000000000000000000131517700303500244700ustar00rootroot00000000000000show role; odyssey-1.5.1-rc8/test/functional/tests/pgoptions/sql/search_path_injection.sql000066400000000000000000000000771517700303500300640ustar00rootroot00000000000000show search_path; -- from injection select * from public.zzz; odyssey-1.5.1-rc8/test/functional/tests/pgoptions/sql/search_paths.sql000066400000000000000000000000221517700303500261730ustar00rootroot00000000000000show search_path; odyssey-1.5.1-rc8/test/functional/tests/pgoptions/sql/several_params.sql000066400000000000000000000001041517700303500265340ustar00rootroot00000000000000show role; show search_path; show statement_timeout; show work_mem; odyssey-1.5.1-rc8/test/functional/tests/pgoptions/sql/spqrguard.sql000066400000000000000000000001401517700303500255400ustar00rootroot00000000000000show spqrguard.prevent_distributed_table_modify; show spqrguard.prevent_reference_table_modify; odyssey-1.5.1-rc8/test/functional/tests/pin_on_listen/000077500000000000000000000000001517700303500230335ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pin_on_listen/expected/000077500000000000000000000000001517700303500246345ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pin_on_listen/expected/1.out000066400000000000000000000003001517700303500255160ustar00rootroot00000000000000listen 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.5.1-rc8/test/functional/tests/pin_on_listen/odyssey.conf000066400000000000000000000010521517700303500253770ustar00rootroot00000000000000daemonize 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 } 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.5.1-rc8/test/functional/tests/pin_on_listen/runner.sh000077500000000000000000000014761517700303500247130ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/pin_on_listen/sql/000077500000000000000000000000001517700303500236325ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/pin_on_listen/sql/1.sql000066400000000000000000000000451517700303500245120ustar00rootroot00000000000000listen example; select pg_sleep(3); odyssey-1.5.1-rc8/test/functional/tests/prep_stmts/000077500000000000000000000000001517700303500223735ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/prep_stmts/.gitignore000066400000000000000000000000141517700303500243560ustar00rootroot00000000000000pstst psmst odyssey-1.5.1-rc8/test/functional/tests/prep_stmts/go.mod000066400000000000000000000001411517700303500234750ustar00rootroot00000000000000module pstmts go 1.23.4 require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 ) odyssey-1.5.1-rc8/test/functional/tests/prep_stmts/go.sum000066400000000000000000000015111517700303500235240ustar00rootroot00000000000000filippo.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.5.1-rc8/test/functional/tests/prep_stmts/pkg/000077500000000000000000000000001517700303500231545ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/prep_stmts/pkg/simple.go000066400000000000000000000024201517700303500247720ustar00rootroot00000000000000package main import ( "context" "github.com/jmoiron/sqlx" "log" "sync" "fmt" _ "github.com/lib/pq" "time" ) 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 { for range 10 { go prep() } log.Println("ITER DONE") time.Sleep(10 * time.Second) } log.Println("TEST OK") } odyssey-1.5.1-rc8/test/functional/tests/prep_stmts/pstmts.conf000066400000000000000000000031701517700303500245750ustar00rootroot00000000000000pid_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 coroutine_stack_size 8 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.5.1-rc8/test/functional/tests/prep_stmts/runner.sh000077500000000000000000000001751517700303500242460ustar00rootroot00000000000000#!/usr/bin/env bash set -ex /usr/bin/odyssey /tests/prep_stmts/pstmts.conf sleep 1 /tests/prep_stmts/pstmts-test ody-stopodyssey-1.5.1-rc8/test/functional/tests/query_processing/000077500000000000000000000000001517700303500235745ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/query_processing/.gitignore000066400000000000000000000000121517700303500255550ustar00rootroot00000000000000results/* odyssey-1.5.1-rc8/test/functional/tests/query_processing/expected/000077500000000000000000000000001517700303500253755ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/query_processing/expected/application_name.out000066400000000000000000000007601517700303500314340ustar00rootroot00000000000000SELECT 'z'; ?column? ---------- z (1 row) SET application_name to 'AAA'; SHOW application_name; application_name ------------------ AAA - 127.0.0.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-a - 127.0.0.1 (1 row) odyssey-1.5.1-rc8/test/functional/tests/query_processing/expected/virtual_show.out000066400000000000000000000007611517700303500306600ustar00rootroot00000000000000SELECT 1; ?column? ---------- 1 (1 row) SET a.b TO 'z'; SHOW a.b; a.b ----- z (1 row) SET odyssey.target_session_attrs = "read-only"; -- should fail SHOW odyssey.target_session_attrs; ERROR: unrecognized configuration parameter "odyssey.target_session_attrs" /* XXX : support this syntax too */ SET odyssey.target_session_attrs TO 'read-write'; -- should succeed SHOW odyssey.target_session_attrs; odyssey.target_session_attrs ------------------------------ read-write (1 row) odyssey-1.5.1-rc8/test/functional/tests/query_processing/odyssey.conf000066400000000000000000000010771517700303500261470ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/query_processing/runner.sh000077500000000000000000000013771517700303500254540ustar00rootroot00000000000000#!/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 cat /tests/query_processing/sql/application_name.sql | psql 'host=localhost port=6432 user=postgres dbname=postgres' --echo-all --quiet > /tests/query_processing/results/application_name.out 2>&1 diff /tests/query_processing/expected/application_name.out /tests/query_processing/results/application_name.out diff /tests/query_processing/expected/virtual_show.out /tests/query_processing/results/virtual_show.out ody-stop odyssey-1.5.1-rc8/test/functional/tests/query_processing/sql/000077500000000000000000000000001517700303500243735ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/query_processing/sql/application_name.sql000066400000000000000000000003011517700303500304110ustar00rootroot00000000000000SELECT '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.5.1-rc8/test/functional/tests/query_processing/sql/virtual_show.sql000066400000000000000000000004241517700303500276420ustar00rootroot00000000000000 SELECT 1; SET a.b TO 'z'; SHOW a.b; SET odyssey.target_session_attrs = "read-only"; -- should fail SHOW odyssey.target_session_attrs; /* XXX : support this syntax too */ SET odyssey.target_session_attrs TO 'read-write'; -- should succeed SHOW odyssey.target_session_attrs;odyssey-1.5.1-rc8/test/functional/tests/reload-show-clients/000077500000000000000000000000001517700303500240565ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/reload-show-clients/Makefile000066400000000000000000000002071517700303500255150ustar00rootroot00000000000000test: show_clients reloads show_clients: /tests/reload-show-clients/reloads.sh reloads: /tests/reload-show-clients/show-clients.sh odyssey-1.5.1-rc8/test/functional/tests/reload-show-clients/config.conf000066400000000000000000000037661517700303500262060ustar00rootroot00000000000000pid_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 coroutine_stack_size 8 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.5.1-rc8/test/functional/tests/reload-show-clients/reloads.sh000077500000000000000000000001471517700303500260500ustar00rootroot00000000000000#!/bin/bash set -e sleep 2 for run in {1..1000}; do kill -s HUP $(pidof odyssey) || exit 1 done odyssey-1.5.1-rc8/test/functional/tests/reload-show-clients/runner.sh000077500000000000000000000012441517700303500257270ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/reload-show-clients/show-clients.sh000077500000000000000000000002321517700303500270310ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/reload/000077500000000000000000000000001517700303500214415ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/reload/conf.conf000066400000000000000000000014601517700303500232360ustar00rootroot00000000000000pid_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 coroutine_stack_size 8 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.5.1-rc8/test/functional/tests/reload/runner.sh000066400000000000000000000010411517700303500233020ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/rule_address/000077500000000000000000000000001517700303500226475ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/rule_address/addr.conf000066400000000000000000000026341517700303500244350ustar00rootroot00000000000000listen { 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 coroutine_stack_size 24odyssey-1.5.1-rc8/test/functional/tests/rule_address/runner.sh000077500000000000000000000044021517700303500245170ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/rule_conn_type/000077500000000000000000000000001517700303500232205ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/rule_conn_type/odyssey.conf000066400000000000000000000020041517700303500255620ustar00rootroot00000000000000listen { 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 no log_session yes log_stats no log_query no coroutine_stack_size 24odyssey-1.5.1-rc8/test/functional/tests/rule_conn_type/runner.sh000077500000000000000000000035061517700303500250740ustar00rootroot00000000000000#!/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' 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' 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' 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.5.1-rc8/test/functional/tests/rules_order/000077500000000000000000000000001517700303500225205ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/rules_order/not_seq_1.conf000066400000000000000000000034631517700303500252650ustar00rootroot00000000000000listen { 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 coroutine_stack_size 24odyssey-1.5.1-rc8/test/functional/tests/rules_order/not_seq_1.out000066400000000000000000000020071517700303500251400ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/rules_order/not_seq_1_reload.conf000066400000000000000000000027051517700303500266110ustar00rootroot00000000000000listen { 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 coroutine_stack_size 24odyssey-1.5.1-rc8/test/functional/tests/rules_order/not_seq_1_reload.out000066400000000000000000000014471517700303500264750ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/rules_order/runner.sh000077500000000000000000000061011517700303500243660ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/rules_order/seq_1.conf000066400000000000000000000035131517700303500244010ustar00rootroot00000000000000listen { 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 coroutine_stack_size 24odyssey-1.5.1-rc8/test/functional/tests/rules_order/seq_1.out000066400000000000000000000020071517700303500242600ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/rules_order/seq_1_reload.conf000066400000000000000000000027351517700303500257340ustar00rootroot00000000000000listen { 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 coroutine_stack_size 24odyssey-1.5.1-rc8/test/functional/tests/rules_order/seq_1_reload.out000066400000000000000000000014471517700303500256150ustar00rootroot00000000000000show 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.5.1-rc8/test/functional/tests/scram/000077500000000000000000000000001517700303500213005ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/scram/config.conf000066400000000000000000000032341517700303500234160ustar00rootroot00000000000000storage "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" } } 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/scram/runner.sh000077500000000000000000000003241517700303500231470ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/scram/test_scram_backend.sh000077500000000000000000000015521517700303500254550ustar00rootroot00000000000000#!/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 odyssey-1.5.1-rc8/test/functional/tests/scram/test_scram_frontend.sh000077500000000000000000000025151517700303500257050ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/server-lifetime/000077500000000000000000000000001517700303500232755ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/server-lifetime/pool_ttl.conf000066400000000000000000000012511517700303500257770ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/server-lifetime/runner.sh000077500000000000000000000031101517700303500251400ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/server-lifetime/server_lifetime.conf000066400000000000000000000012601517700303500273270ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/shared_pool/000077500000000000000000000000001517700303500224725ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/shared_pool/odyssey.conf000066400000000000000000000015211517700303500250370ustar00rootroot00000000000000daemonize 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 } 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.5.1-rc8/test/functional/tests/shared_pool/runner.sh000077500000000000000000000016621517700303500243470ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/shell-test/000077500000000000000000000000001517700303500222575ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/shell-test/conf.conf000066400000000000000000000056731517700303500240660ustar00rootroot00000000000000pid_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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/shell-test/conf2.conf000066400000000000000000000055351517700303500241450ustar00rootroot00000000000000pid_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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/shell-test/console_role_test.sh000077500000000000000000000033251517700303500263430ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/shell-test/log_error_rate_test.sh000077500000000000000000000005601517700303500266630ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/shell-test/override_pg_options_test.sh000077500000000000000000000022511517700303500277350ustar00rootroot00000000000000 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.5.1-rc8/test/functional/tests/shell-test/parse_pg_options_test.sh000077500000000000000000000140211517700303500272260ustar00rootroot00000000000000 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.5.1-rc8/test/functional/tests/shell-test/pool_size.conf000066400000000000000000000012601517700303500251300ustar00rootroot00000000000000pid_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 coroutine_stack_size 16 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.5.1-rc8/test/functional/tests/shell-test/pool_size_test.sh000077500000000000000000000007161517700303500256640ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/shell-test/runner.sh000077500000000000000000000020301517700303500241220ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/shell-test/test_sleep.sh000077500000000000000000000004261517700303500247670ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/show-unix-socket-ports/000077500000000000000000000000001517700303500245675ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/show-unix-socket-ports/conf.conf000066400000000000000000000012461517700303500263660ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/functional/tests/show-unix-socket-ports/runner.sh000066400000000000000000000023371517700303500264410ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/startup-notice/000077500000000000000000000000001517700303500231545ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/startup-notice/.gitignore000066400000000000000000000000121517700303500251350ustar00rootroot00000000000000results/* odyssey-1.5.1-rc8/test/functional/tests/startup-notice/expected/000077500000000000000000000000001517700303500247555ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/startup-notice/expected/1.out000066400000000000000000000002261517700303500256460ustar00rootroot00000000000000WARNING: database "broken_collation" has no actual collation version, but a version was recorded select 42; ?column? ---------- 42 (1 row) odyssey-1.5.1-rc8/test/functional/tests/startup-notice/odyssey.conf000066400000000000000000000011141517700303500255170ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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 "broken_collation" { user "postgres" { authentication "none" storage "postgres_server" storage_user "postgres" pool "session" pool_discard no pool_smart_discard no } } odyssey-1.5.1-rc8/test/functional/tests/startup-notice/runner.sh000077500000000000000000000012771517700303500250330ustar00rootroot00000000000000#!/bin/bash set -ex check_file() { local name=$1 local user=$2 cat /tests/startup-notice/sql/$name.sql | psql "host=localhost port=6432 user=$user dbname=broken_collation" --echo-all --no-psqlrc --quiet > /tests/startup-notice/results/$name.out 2>&1 || { cat /tests/startup-notice/results/$name.out exit 1 } diff /tests/startup-notice/expected/$name.out /tests/startup-notice/results/$name.out || { cat /tests/startup-notice/results/$name.out xxd /tests/startup-notice/results/$name.out exit 1 } } mkdir -p /tests/startup-notice/results/ /usr/bin/odyssey /tests/startup-notice/odyssey.conf sleep 1 check_file '1' postgres ody-stop odyssey-1.5.1-rc8/test/functional/tests/startup-notice/sql/000077500000000000000000000000001517700303500237535ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/startup-notice/sql/1.sql000066400000000000000000000000131517700303500246260ustar00rootroot00000000000000select 42; odyssey-1.5.1-rc8/test/functional/tests/tls-compat/000077500000000000000000000000001517700303500222565ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/tls-compat/config.conf000066400000000000000000000025421517700303500243750ustar00rootroot00000000000000daemonize 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 "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" } 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.5.1-rc8/test/functional/tests/tls-compat/runner.sh000077500000000000000000000024141517700303500241270ustar00rootroot00000000000000#!/bin/bash -x set -ex 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 # 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 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 sleep 1 ody-stop odyssey-1.5.1-rc8/test/functional/tests/transactional/000077500000000000000000000000001517700303500230355ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/transactional/f.sql000066400000000000000000000000271517700303500240020ustar00rootroot00000000000000\! sleep 2 select 42; odyssey-1.5.1-rc8/test/functional/tests/transactional/odyssey.conf000066400000000000000000000012671517700303500254110ustar00rootroot00000000000000daemonize 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 } 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.5.1-rc8/test/functional/tests/transactional/runner.sh000077500000000000000000000021451517700303500247070ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/transactional_load/000077500000000000000000000000001517700303500240345ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/transactional_load/f.sql000066400000000000000000000000011517700303500247710ustar00rootroot00000000000000;odyssey-1.5.1-rc8/test/functional/tests/transactional_load/odyssey.conf000066400000000000000000000015241517700303500264040ustar00rootroot00000000000000daemonize 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 # 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.5.1-rc8/test/functional/tests/transactional_load/runner.sh000077500000000000000000000004741517700303500257110ustar00rootroot00000000000000#!/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.5.1-rc8/test/functional/tests/tsa/000077500000000000000000000000001517700303500207625ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/tsa/runner.sh000077500000000000000000000012451517700303500226340ustar00rootroot00000000000000#!/bin/bash -x set -ex /usr/bin/odyssey /tests/tsa/tsa.conf sleep 1 psql -h localhost -p 6432 -U user_ro -c "SELECT pg_is_in_recovery()" tsa_db | grep 't' > /dev/null 2>&1 || { echo "ERROR: failed auth with hba trust, correct password and plain password in config" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-repl.log exit 1 } psql -h localhost -p 6432 -U user_rw -c "SELECT pg_is_in_recovery()" tsa_db | grep 'f' > /dev/null 2>&1 || { echo "ERROR: failed auth with hba trust, correct password and plain password in config" cat /var/log/odyssey.log echo " " cat /var/log/postgresql/postgresql-16-main.log exit 1 } ody-stop odyssey-1.5.1-rc8/test/functional/tests/tsa/tsa.conf000066400000000000000000000013001517700303500224120ustar00rootroot00000000000000storage "postgres_server" { type "remote" host "[127.0.0.1]:5432,[localhost]:5433" } database "tsa_db" { user "user_ro" { authentication "none" storage "postgres_server" pool "session" target_session_attrs "read-only" } user "user_rw" { authentication "none" storage "postgres_server" pool "session" target_session_attrs "read-write" } } 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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/tsa_ports/000077500000000000000000000000001517700303500222115ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/tsa_ports/check_is_master.sql000066400000000000000000000000441517700303500260530ustar00rootroot00000000000000select check_is_in_recovery(false); odyssey-1.5.1-rc8/test/functional/tests/tsa_ports/check_is_replica.sql000066400000000000000000000000421517700303500261750ustar00rootroot00000000000000select check_is_in_recovery(true);odyssey-1.5.1-rc8/test/functional/tests/tsa_ports/create_check_fn.sql000066400000000000000000000004531517700303500260170ustar00rootroot00000000000000create or replace function check_is_in_recovery(expected boolean) returns integer as $$ begin if pg_is_in_recovery() is distinct from expected then raise exception 'pg_is_in_recovery()=% but expected=%', pg_is_in_recovery(), expected; end if; return 1; end; $$ language plpgsql; odyssey-1.5.1-rc8/test/functional/tests/tsa_ports/odyssey.conf000066400000000000000000000013421517700303500245570ustar00rootroot00000000000000daemonize 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 no log_stats no workers 2 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 tls "disable" target_session_attrs "read-only" } listen { host "127.0.0.1" port 6433 tls "disable" target_session_attrs "read-write" } storage "postgres_server" { type "remote" host "127.0.0.1:5432,127.0.0.1:5433" } database "postgres" { user "postgres" { authentication "none" storage "postgres_server" pool "session" pool_timeout 1000 pool_size 50 pool_notice_after_waiting_ms 500 } } odyssey-1.5.1-rc8/test/functional/tests/tsa_ports/runner.sh000077500000000000000000000030661517700303500240660ustar00rootroot00000000000000#!/bin/bash set -ex /usr/bin/odyssey /tests/tsa_ports/odyssey.conf sleep 1 psql 'host=localhost port=6432 user=postgres dbname=postgres' -c 'select pg_is_in_recovery()' | grep 't' || { echo 'exptected to port 6432 return connect to replica' exit 1 } psql 'host=localhost port=6433 user=postgres dbname=postgres' -c 'select pg_is_in_recovery()' | grep 'f' || { echo 'exptected to port 6433 return connect to master' exit 1 } psql 'host=localhost port=6433 user=postgres dbname=postgres' -f /tests/tsa_ports/create_check_fn.sql # limits are 50 but we an use up to 100 connections, 50 per each endpoint pgbench 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -j 2 -c 50 -f /tests/tsa_ports/check_is_replica.sql --no-vacuum --progress 1 -T 5 --max-tries=1 & pgbench 'host=localhost port=6433 user=postgres dbname=postgres sslmode=disable' -j 2 -c 50 -f /tests/tsa_ports/check_is_master.sql --no-vacuum --progress 1 -T 5 --max-tries=1 & wait -n || { cat /var/log/odyssey.log exit 1 } wait -n || { cat /var/log/odyssey.log exit 1 } pgbench 'host=localhost port=6432 user=postgres dbname=postgres sslmode=disable' -j 2 -c 50 -f /tests/tsa_ports/check_is_replica.sql --connect --no-vacuum --progress 1 -T 5 --max-tries=1 & pgbench 'host=localhost port=6433 user=postgres dbname=postgres sslmode=disable' -j 2 -c 50 -f /tests/tsa_ports/check_is_master.sql --connect --no-vacuum --progress 1 -T 5 --max-tries=1 & wait -n || { cat /var/log/odyssey.log exit 1 } wait -n || { cat /var/log/odyssey.log exit 1 } ody-stopodyssey-1.5.1-rc8/test/functional/tests/unix-socket-storage/000077500000000000000000000000001517700303500241065ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/functional/tests/unix-socket-storage/conf.conf000066400000000000000000000010161517700303500257000ustar00rootroot00000000000000storage "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 coroutine_stack_size 24 listen { host "127.0.0.1" port 6432 } odyssey-1.5.1-rc8/test/functional/tests/unix-socket-storage/runner.sh000077500000000000000000000004441517700303500257600ustar00rootroot00000000000000#!/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.5.1-rc8/test/jemalloc/000077500000000000000000000000001517700303500164555ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/jemalloc/Dockerfile000066400000000000000000000034651517700303500204570ustar00rootroot00000000000000FROM 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/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 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/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 \ build-essential \ autoconf \ postgresql-client postgresql-client-common postgresql-contrib \ python3 \ curl \ libunwind-dev \ 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.5.1-rc8/test/jemalloc/docker-compose.yml000066400000000000000000000010011517700303500221020ustar00rootroot00000000000000services: 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.5.1-rc8/test/jemalloc/entrypoint.sh000077500000000000000000000012401517700303500212240ustar00rootroot00000000000000#!/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.5.1-rc8/test/jemalloc/odyssey.conf000066400000000000000000000015541517700303500210300ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/oom/000077500000000000000000000000001517700303500154615ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/oom/Dockerfile000066400000000000000000000004731517700303500174570ustar00rootroot00000000000000FROM ubuntu:latest RUN apt-get update -o Acquire::AllowInsecureRepositories=true && apt-get install -y --no-install-recommends --allow-unauthenticated \ postgresql-contrib \ sudo COPY ./test/oom/entrytest.sh /usr/local/bin/entrytest.sh RUN chmod +x /usr/local/bin/entrytest.sh ENTRYPOINT ["entrytest.sh"]odyssey-1.5.1-rc8/test/oom/docker-compose.yml000066400000000000000000000015721517700303500211230ustar00rootroot00000000000000services: 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.5.1-rc8/test/oom/entrytest.sh000066400000000000000000000014251517700303500200600ustar00rootroot00000000000000#!/bin/bash set -e until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." sleep 1 done psql 'host=odyssey port=6432 user=postgres dbname=postgres' -c 'select 1' || { echo "error: failed to make query" ody-stop 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 output=$(pgbench 'host=odyssey port=6432 user=postgres dbname=postgres' -c $c -j $c -t $DURATION -f /tmp/load.txt -C 2>&1 || true) if echo "$output" | grep -q "soft out of memory" ; then echo "OK: Soft oom found!" exit 0 fi done echo "ERROR: Soft oom not found" exit 1 odyssey-1.5.1-rc8/test/oom/odyssey.conf000066400000000000000000000012111517700303500200220ustar00rootroot00000000000000unix_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 coroutine_stack_size 24 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.5.1-rc8/test/prom-exporter/000077500000000000000000000000001517700303500175125ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/prom-exporter/Dockerfile000066400000000000000000000005051517700303500215040ustar00rootroot00000000000000FROM golang:1.25.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.5.1-rc8/test/prom-exporter/docker-compose.yml000066400000000000000000000013251517700303500231500ustar00rootroot00000000000000services: 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.5.1-rc8/test/prom-exporter/entrypoint.sh000077500000000000000000000034641517700303500222730ustar00rootroot00000000000000#!/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.5.1-rc8/test/prom-exporter/expected.out000066400000000000000000000074261517700303500220550ustar00rootroot00000000000000odyssey_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_waiting_route{database="console",user="console"} 1 odyssey_client_pool_waiting_route{database="postgres",user="postgres"} 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_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_READ"} 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_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.5.1-rc8/test/prom-exporter/test.conf000066400000000000000000000011271517700303500213410ustar00rootroot00000000000000unix_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 coroutine_stack_size 128 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.5.1-rc8/test/prometheus-legacy/000077500000000000000000000000001517700303500203245ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/prometheus-legacy/Dockerfile000066400000000000000000000022531517700303500223200ustar00rootroot00000000000000FROM ubuntu:noble 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 \ libmicrohttpd-dev \ postgresql-client-common postgresql-client \ python3 \ wget \ net-tools \ curl \ 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.5.1-rc8/test/prometheus-legacy/config.conf000066400000000000000000000010311517700303500224330ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/prometheus-legacy/docker-compose.yml000066400000000000000000000014721517700303500237650ustar00rootroot00000000000000services: 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.5.1-rc8/test/prometheus-legacy/entrypoint.sh000077500000000000000000000020231517700303500230730ustar00rootroot00000000000000#!/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.5.1-rc8/test/prometheus-legacy/prometheus.yml000066400000000000000000000002611517700303500232410ustar00rootroot00000000000000global: scrape_interval: 1s scrape_configs: - job_name: 'odyssey' static_configs: - targets: ['odyssey:7777'] fallback_scrape_protocol: "PrometheusText0.0.4" odyssey-1.5.1-rc8/test/proto/000077500000000000000000000000001517700303500160325ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/proto/Dockerfile000066400000000000000000000037721517700303500200350ustar00rootroot00000000000000FROM 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 \ 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 / RUN git clone --depth 1 https://github.com/pg-sharding/spqr.git 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.5.1-rc8/test/proto/docker-compose.yml000066400000000000000000000016101517700303500214650ustar00rootroot00000000000000services: 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}" 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.5.1-rc8/test/proto/entrypoint.sh000066400000000000000000000012031517700303500205750ustar00rootroot00000000000000#!/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 \ -test.run 'Test[^(DeallocatePrepareRemovesPstmtsByXproto|DeallocateRemovesPstmtsByXproto)]' } do_test suser do_test tuser ody-stopodyssey-1.5.1-rc8/test/proto/odyssey.conf000066400000000000000000000015011517700303500203750ustar00rootroot00000000000000unix_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 coroutine_stack_size 24 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.5.1-rc8/test/quickstart/000077500000000000000000000000001517700303500170615ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/quickstart/Dockerfile000066400000000000000000000004331517700303500210530ustar00rootroot00000000000000FROM 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.5.1-rc8/test/quickstart/docker-compose.yml000066400000000000000000000013121517700303500225130ustar00rootroot00000000000000services: 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.5.1-rc8/test/quickstart/entrytest.sh000066400000000000000000000010151517700303500214530ustar00rootroot00000000000000#!/bin/sh set -ex until pg_isready -h primary -p 5432 -U postgres -d postgres; do echo "Wait for primary..." 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" 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 make pgbench query" ody-stop exit 1 } odyssey-1.5.1-rc8/test/quickstart/test.conf000066400000000000000000000011251517700303500207060ustar00rootroot00000000000000unix_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 coroutine_stack_size 24 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.5.1-rc8/test/replication/000077500000000000000000000000001517700303500172005ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/replication/Dockerfile000066400000000000000000000005121517700303500211700ustar00rootroot00000000000000FROM ubuntu:resolute RUN apt-get update -y && apt-get install -y postgresql-client postgresql-client-common postgresql-contrib 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.5.1-rc8/test/replication/docker-compose.yml000066400000000000000000000021221517700303500226320ustar00rootroot00000000000000services: 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: - ./test.conf:/etc/odyssey/odyssey.conf networks: - od_replication_test depends_on: primary: condition: service_healthy tester: build: context: ../../ # odyssey root dir dockerfile: ./test/replication/Dockerfile networks: - od_replication_test depends_on: - odyssey networks: od_replication_test: driver: bridge odyssey-1.5.1-rc8/test/replication/entrytest.sh000066400000000000000000000074501517700303500216030ustar00rootroot00000000000000#!/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$" } do_logical_repl_test do_physical_repl_test odyssey-1.5.1-rc8/test/replication/logical_expected.out000066400000000000000000000017131517700303500232260ustar00rootroot00000000000000=== 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.5.1-rc8/test/replication/pg_hba.conf000066400000000000000000000005361517700303500212730ustar00rootroot00000000000000local 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.5.1-rc8/test/replication/test.conf000066400000000000000000000011601517700303500210240ustar00rootroot00000000000000unix_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 coroutine_stack_size 24 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 "transaction" pool_size 10 client_fwd_error yes } } database "console" { user "console" { authentication "none" role "admin" pool "session" storage "local" } }odyssey-1.5.1-rc8/test/stress/000077500000000000000000000000001517700303500162125ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/stress/Dockerfile000066400000000000000000000051471517700303500202130ustar00rootroot00000000000000FROM 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/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 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/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 \ openssh-server \ vim \ gdb \ build-essential \ strace ltrace \ lsof \ 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.5.1-rc8/test/stress/docker-compose.yml000066400000000000000000000027461517700303500216600ustar00rootroot00000000000000services: 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.5.1-rc8/test/stress/entrypoint.sh000077500000000000000000000023361517700303500207700ustar00rootroot00000000000000#!/usr/bin/env bash # TODO: create fuzzing tests here set -eux 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 /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 2s \ -small-max-latency 1s \ -prepared-clients 50 \ -prepared-rows 10 \ -prepared-think-min 20ms \ -prepared-think-max 500ms \ -prepared-timeout 2s \ -prepared-max-latency 2s \ -tx-clients 4 \ -tx-sleep 1s \ -tx-think-min 100ms \ -tx-think-max 200ms \ -tx-timeout 2s \ -tx-max-latency 2s \ -elephant-clients 4 \ -elephant-chunk-bytes 65536 \ -elephant-chunks 20 \ -elephant-row-delay 1s \ -elephant-drop-prob 0.15 \ -elephant-timeout 60s \ -elephant-max-factor 1.2 \ -elephant-max-duration 50s || { cat /odyssey.log for i in /asan-output*; do cat $i done exit 1 } ody-stop odyssey-1.5.1-rc8/test/stress/go.mod000066400000000000000000000003741517700303500173240ustar00rootroot00000000000000module 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.5.1-rc8/test/stress/go.sum000066400000000000000000000043731517700303500173540ustar00rootroot00000000000000github.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.5.1-rc8/test/stress/odyssey.conf000066400000000000000000000016131517700303500205610ustar00rootroot00000000000000daemonize 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 coroutine_stack_size 24 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.5.1-rc8/test/stress/pkg/000077500000000000000000000000001517700303500167735ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/stress/pkg/main.go000066400000000000000000000725231517700303500202570ustar00rootroot00000000000000package 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{}, 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 } gauges.ElephantActive.Add(1) start := time.Now() streamCtx, cancel := context.WithTimeout(ctx, cfg.ElephantQueryTimeout) 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(): } }() 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, cfg, i, gauges, events, &wg) } wg.Wait() close(events) <-collectorDone if collector.failed { os.Exit(2) } } odyssey-1.5.1-rc8/test/stress/primary-init/000077500000000000000000000000001517700303500206365ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/stress/primary-init/01-append-hba.sh000066400000000000000000000003361517700303500234110ustar00rootroot00000000000000#!/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.5.1-rc8/test/stress/primary-init/02-set-max-connections.sh000066400000000000000000000001531517700303500253060ustar00rootroot00000000000000#!/bin/bash set -eux sed -i "s/max_connections = 100/max_connections = 400/g" "$PGDATA/postgresql.conf" odyssey-1.5.1-rc8/test/stress/primary-init/03-create-users.sql000066400000000000000000000010741517700303500242030ustar00rootroot00000000000000CREATE 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.5.1-rc8/test/stress/primary-init/04-log-locks.sql000066400000000000000000000002351517700303500234720ustar00rootroot00000000000000ALTER 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.5.1-rc8/test/stress/primary-init/05-hot-standby-feedback.sql000066400000000000000000000001051517700303500255530ustar00rootroot00000000000000ALTER SYSTEM SET hot_standby_feedback = on; SELECT pg_reload_conf(); odyssey-1.5.1-rc8/test/stress/replica-init/000077500000000000000000000000001517700303500205725ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/stress/replica-init/10-setup-standby.sh000066400000000000000000000004741517700303500241530ustar00rootroot00000000000000#!/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.5.1-rc8/test/unit/000077500000000000000000000000001517700303500156465ustar00rootroot00000000000000odyssey-1.5.1-rc8/test/unit/Dockerfile000066400000000000000000000013011517700303500176330ustar00rootroot00000000000000FROM ubuntu:noble ARG build_type=build_release 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 \ 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" ]